Rust 的型別系統在編譯時期就能確保變數的型別安全,避免執行時期的錯誤。變數宣告使用 let
關鍵字,並可選擇性地加上型別註解。數值型別包含整數和浮點數,支援常用的數學運算子。型別轉換需明確使用 as
關鍵字,但需注意潛在的溢位問題。浮點數運算的精確度限制需要使用 epsilon 值進行比較,避免非預期的結果。對於複數、有理數等更進階的數值型別,可以透過引入 num
這樣的外部函式庫來擴充套件 Rust 的功能。
變數定義和函式呼叫
讓我們透過一個簡短的例子來介紹一些基礎概念:定義帶有型別註解的變數和呼叫函式。以下程式碼列印 a + b = 30
到控制檯。從程式碼中可以看到,Rust 提供了多種語法來為整數註解資料型別,你可以根據具體情況選擇最自然的方式。這個例子的原始碼位於 ch2/ch2-first-steps.rs
。
fn main() {
// 定義變數 a,並指定為 10
let a = 10;
// 定義變數 b,並指定其型別為 i32,指定為 20
let b: i32 = 20;
// 定義變數 c,並指定其型別為 i32,指定為 30
let c = 30i32;
// 定義變數 d,並指定其型別為 i32,指定為 30
let d = 30_i32;
// 呼叫 add 函式,計算 a + b 和 c + d 的結果,並將結果相加
let e = add(add(a, b), add(c, d));
}
內容解密:
fn main()
定義了程式的入口點,即main
函式。let
關鍵字用於定義變數。在 Rust 中,變數預設是不可變的。a
、b
、c
和d
是變數名稱,它們被賦予了不同的整數值。i32
是 Rust 中的一種整數型別,表示 32 位帶符號整數。add
函式是一個計算兩個整數相加的函式,它在這裡被呼叫多次以計算最終結果。- Rust 的型別系統允許開發者明確指定變數的型別,也可以讓編譯器根據指定推斷出變數的型別。
圖表翻譯:
flowchart TD A[定義變數 a] --> B[定義變數 b] B --> C[定義變數 c] C --> D[定義變數 d] D --> E[計算 a + b] E --> F[計算 c + d] F --> G[計算 (a + b) + (c + d)] G --> H[輸出結果]
這個流程圖展示了程式中變數定義和計算的順序,從定義變數開始,到計算最終結果並輸出。每一步驟都對應著程式碼中的特定操作。
Rust 基礎語法與變數宣告
Rust 是一種強大的程式設計語言,注重安全性和效能。在這篇文章中,我們將探討 Rust 的基礎語法和變數宣告。
函式宣告
在 Rust 中,函式是使用 fn
關鍵字宣告的。每個 Rust 程式都需要一個 main
函式作為入口點。main
函式不需要任何引數,也不需要傳回任何值。
fn main() {
// 程式碼放在這裡
}
變數宣告
在 Rust 中,變數是使用 let
關鍵字宣告的。變數可以是不可變的(immutable)或可變的(mutable)。預設情況下,變數是不可變的。
let a = 10; // 宣告一個不可變的變數 a
let mut b = 20; // 宣告一個可變的變數 b
資料型別
Rust 是一種靜態型別語言,這意味著它可以在編譯時期確定變數的資料型別。您可以使用冒號(:)來指定變數的資料型別。
let a: i32 = 10; // 宣告一個 32 位元整數變數 a
數值字面量
Rust 的數值字面量可以包括型別註解。這對於複雜的數學表示式很有用。
let a = 10i32; // 宣告一個 32 位元整數變數 a
下劃線與數值
Rust 的數值字面量可以包含下劃線(_),這可以增加數值的可讀性。
let a = 1_000_000; // 宣告一個整數變數 a
函式宣告與型別
當宣告函式時,需要指定函式引數和傳回值的型別。
fn add(a: i32, b: i32) -> i32 {
a + b
}
在這個例子中,add
函式接受兩個 32 位元整數引數,並傳回一個 32 位元整數結果。
數字與運算
在程式設計中,數字是基本的資料型別。Rust 支援多種數字型別,包括整數和浮點數。
整數
整數是沒有小數點的數字,例如 1、2、3 等。Rust 的整數型別包括 i8
、i16
、i32
、i64
和 i128
,分別代表 8 位元、16 位元、32 位元、64 位元和 128 位元的整數。
let a: i32 = 10; // 32 位元整數
let b: i64 = 20; // 64 位元整數
浮點數
浮點數是有小數點的數字,例如 3.14 或 -0.5。Rust 的浮點數型別包括 f32
和 f64
,分別代表 32 位元和 64 位元的浮點數。
let pi: f64 = 3.14159; // 64 位元浮點數
let e: f32 = -0.5; // 32 位元浮點數
運算
Rust 支援多種運算,包括加法、減法、乘法和除法。運算使用 infix notation,即運算子號放在運算元之間。
let a = 10;
let b = 20;
let sum = a + b; // 加法
let difference = a - b; // 減法
let product = a * b; // 乘法
let quotient = a / b; // 除法
方法
Rust 的數字型別也可以使用方法。例如,round()
方法可以將浮點數四捨五入為最接近的整數。
let pi = 3.14159;
let rounded_pi = pi.round(); // 四捨五入為 3
轉換
Rust 的數字型別之間可以進行轉換,但必須明確指定轉換型別。
let a: i32 = 10;
let b: i64 = a as i64; // 將 i32 轉換為 i64
範例
以下是範例程式,示範了 Rust 的數字型別和運算。
fn main() {
let twenty = 20;
let twenty_one: i32 = 21;
let twenty_two = 22i32;
let addition = twenty + twenty_one + twenty_two;
println!("{} + {} + {} = {}", twenty, twenty_one, twenty_two, addition);
let one_million: i64 = 1_000_000;
println!("{}", one_million);
}
這個範例程式會輸出以下結果:
20 + 21 + 22 = 63
1000000
數值字面量與基本運算
Rust 支援多種數值字面量,包括基數 2(二進位制)、基數 8(八進位制)和基數 16(十六進位制)。這些數值字面量可以用於定義整數,並且也可以在格式化巨集中使用,例如 println!
。
基數表示
Rust 中的整數可以使用不同基數來表示,包括基數 10(十進位制)、基數 2(二進位制)、基數 8(八進位制)和基數 16(十六進位制)。以下是基數表示的範例:
let three = 0b11; // 基數 2(二進位制)
let thirty = 0o36; // 基數 8(八進位制)
let three_hundred = 0x12C; // 基數 16(十六進位制)
格式化輸出
Rust 的 println!
巨集可以用於格式化輸出。以下是使用不同基數輸出的範例:
println!("base 10: {} {} {}", three, thirty, three_hundred);
println!("base 2: {:b} {:b} {:b}", three, thirty, three_hundred);
println!("base 8: {:o} {:o} {:o}", three, thirty, three_hundred);
println!("base 16: {:x} {:x} {:x}", three, thirty, three_hundred);
這些輸出將會顯示為:
base 10: 3 30 300
base 2: 11 11110 100101100
base 8: 3 36 454
base 16: 3 1e 12c
數值字面量與基本運算
Rust 支援基本的算術運算,包括加法、減法、乘法和除法。以下是範例:
let a = 10;
let b = 3;
let sum = a + b;
let difference = a - b;
let product = a * b;
let quotient = a / b;
println!("sum: {}", sum);
println!("difference: {}", difference);
println!("product: {}", product);
println!("quotient: {}", quotient);
這些輸出將會顯示為:
sum: 13
difference: 7
product: 30
quotient: 3
數字與比較運算
在程式設計中,數字是基本的資料型別。Rust 支援多種數字型別,包括有號整數(i)、無號整數(u)和浮點數(f)。這些型別可以用來代表不同範圍的數值。
數字型別
Rust 的數字型別可以分為以下幾類別:
- 有號整數(i):可以代表負數和正數,例如
i8
、i16
、i32
和i64
。 - 無號整數(u):只能代表正數,但可以達到比有號整數更高的值,例如
u8
、u16
、u32
和u64
。 - 浮點數(f):可以代表實數,包括特殊的位元模式來表示無窮大、負無窮大和非數值,例如
f32
和f64
。
數字比較
Rust 的數字型別支援多種比較運算,包括:
- 等於(==)
- 不等於(!=)
- 大於(>)
- 小於(<)
- 大於或等於(>=)
- 小於或等於(<=)
這些比較運算可以用來比較兩個數字之間的關係。
Traits
Rust 的 Traits 是一種用來定義分享行為的機制。它允許你定義一組方法和屬性,可以被多個型別實作。Traits 是 Rust 中的一個重要概念,它可以幫助你寫出更靈活和可重用的程式碼。
範例程式碼
以下是一個簡單的範例程式碼,示範如何使用 Rust 的數字型別和比較運算:
fn main() {
let a = 10;
let b = 20;
println!("a == b: {}", a == b);
println!("a!= b: {}", a!= b);
println!("a > b: {}", a > b);
println!("a < b: {}", a < b);
println!("a >= b: {}", a >= b);
println!("a <= b: {}", a <= b);
}
這個程式碼定義了兩個變數 a
和 b
,然後使用比較運算來比較它們之間的關係。最後,它使用 println!
宏來輸出比較結果。
內容解密:
在這個範例程式碼中,我們使用了 Rust 的數字型別和比較運算來比較兩個變數之間的關係。比較運算的結果會被輸出到螢幕上。這個程式碼示範瞭如何使用 Rust 的基本資料型別和運運算元來寫出簡單的程式碼。
圖表翻譯:
以下是這個範例程式碼的流程圖:
graph LR A[定義變數 a 和 b] --> B[比較 a 和 b] B --> C[輸出比較結果] C --> D[結束程式]
這個流程圖示範了這個範例程式碼的執行流程。首先,我們定義了兩個變數 a
和 b
,然後比較它們之間的關係,最後輸出比較結果。
數值比較與轉換
在 Rust 中,數值的比較和轉換是根據特定的規則和限制。首先,Rust 的型別安全機制不允許直接比較不同型別的數值。例如,以下程式碼不會編譯透過:
fn main() {
let a: i32 = 10;
let b: u16 = 100;
if a < b {
println!("Ten is less than one hundred.");
}
}
為了使程式碼透過編譯,我們需要使用 as
運算子將其中一個運算元轉換為另一個型別。以下程式碼示範瞭如何將 b
轉換為 i32
:
fn main() {
let a: i32 = 10;
let b: u16 = 100;
if a < (b as i32) {
println!("Ten is less than one hundred.");
}
}
一般來說,將小型別轉換為大型別(例如,16 位元轉換為 32 位元)是最安全的做法,這被稱為「提升」(promotion)。在這種情況下,我們也可以將 a
降級為 u16
,但這樣做通常風險更高。
使用 as
運算子進行型別轉換需要謹慎,因為它可能導致程式行為不可預測。例如,表示式 300_i32 as i8
會傳回 44。
在某些情況下,使用 as
運算子可能過於限制。可以透過引入一些額外的程式碼來還原對型別轉換過程的控制。
數學運算子
Rust 支援多種數學運算子,包括:
運算子 | Rust 語法 | 示例 |
---|---|---|
小於 | < | 1.0 < 2.0 |
大於 | > | 2.0 > 1.0 |
等於 | == | 1.0 == 1.0 |
不等於 | != | 1.0!= 2.0 |
小於或等於 | <= | 1.0 <= 2.0 |
大於或等於 | >= | 2.0 >= 1.0 |
這些運算子可以用於比較和轉換數值。
圖表翻譯
graph LR A[數值比較] --> B[使用 as 運算子] B --> C[提升或降級] C --> D[數學運算子] D --> E[小於、大於、等於、不等於、小於或等於、大於或等於]
圖表翻譯:
此圖表示了 Rust 中的數值比較和轉換過程。首先,需要使用 as
運算子進行型別轉換。然後,可以進行提升或降級。最後,可以使用數學運算子進行比較和運算。
Rust 程式設計基礎
Rust是一種強大的程式設計語言,提供了許多功能以確保程式的安全性和效率。在本章中,我們將探討Rust的語言基礎,包括型別轉換、特徵(traits)和錯誤處理。
型別轉換
在Rust中,型別轉換可以使用try_into()
方法實作。以下程式碼示範瞭如何使用try_into()
方法將u16
型別轉換為i32
型別:
use std::convert::TryInto;
fn main() {
let a: i32 = 10;
let b: u16 = 100;
let b_ = b.try_into().unwrap();
if a < b_ {
println!("Ten is less than one hundred.");
}
}
在這個程式碼中,try_into()
方法傳回一個Result
型別的值,該值包含了轉換結果。如果轉換成功,則傳回轉換後的值;如果轉換失敗,則傳回錯誤值。
特徵(Traits)
Rust的特徵(traits)是一種定義方法集合的方式,可以用於實作多型性。特徵可以被視為抽象類別或介面。在上面的程式碼中,TryInto
特徵被用於實作try_into()
方法。
錯誤處理
Rust的錯誤處理機制是根據Result
型別的。Result
型別可以包含兩種值:成功值和錯誤值。在上面的程式碼中,unwrap()
方法被用於處理成功值。如果轉換失敗,則會傳回錯誤值。
浮點數危險
浮點數型別(如f32
和f64
)可能會導致嚴重的錯誤。浮點數型別通常使用二進製表示法,但我們經常需要計算十進位制數字。這種差異可能會導致精確度問題。
內容解密:
在這個章節中,我們學習瞭如何使用try_into()
方法實作型別轉換,以及如何使用特徵(traits)定義方法集合。同時,我們也瞭解了Rust的錯誤處理機制和浮點數型別的危險。透過這些知識,我們可以更好地掌握Rust的語言基礎,並寫出更安全、更高效的程式碼。
圖表翻譯:
graph LR A[型別轉換] --> B[try_into()方法] B --> C[Result型別] C --> D[成功值或錯誤值] D --> E[unwrap()方法] E --> F[處理成功值或錯誤值]
在這個圖表中,我們展示了型別轉換的過程,從使用try_into()
方法到處理成功值或錯誤值。這個圖表可以幫助我們更好地理解Rust的型別轉換和錯誤處理機制。
浮點數的陷阱
浮點數是一種資料型別,旨在代表實數,但它們的精確度是有限的。這種限制會導致一些意想不到的行為,尤其是在比較浮點數時。浮點數通常使用二進位制(base 2)來表示,但我們常常需要它們來進行十進位制(base 10)數字的運算。這種不匹配會產生模糊性。
浮點數的精確度問題
浮點數的精確度問題源於它們不能準確地代表所有十進位制數字。例如,0.1 在二進位中沒有精確的表示。這意味著當你在程式中使用浮點數時,你可能會遇到一些意想不到的結果。
比較浮點數
比較浮點數是一個常見的問題,因為浮點數的精確度問題會導致比較結果不準確。例如,以下程式碼可能會失敗:
fn main() {
assert!(0.1 + 0.2 == 0.3);
}
這個程式碼可能會因為浮點數的精確度問題而失敗。為瞭解決這個問題,我們可以使用一個小的誤差值來比較浮點數。
使用誤差值比較浮點數
我們可以使用一個小的誤差值來比較浮點數。例如:
fn main() {
let epsilon = 1e-6;
assert!((0.1 + 0.2 - 0.3).abs() < epsilon);
}
這個程式碼會檢查 0.1 + 0.2
和 0.3
之間的差值是否小於一個小的誤差值 epsilon
。如果差值小於 epsilon
,則認為兩個浮點數相等。
內容解密
- 浮點數的精確度問題:浮點數不能準確地代表所有十進位制數字。
- 比較浮點數:比較浮點數可能會因為精確度問題而失敗。
- 使用誤差值比較浮點數:使用一個小的誤差值來比較浮點數,可以解決精確度問題。
圖表翻譯
flowchart TD A[浮點數] --> B[精確度問題] B --> C[比較失敗] C --> D[使用誤差值] D --> E[比較成功]
這個流程圖展示了浮點數的精確度問題如何導致比較失敗,以及如何使用誤差值來解決這個問題。
浮點數運算的陷阱
在進行浮點數運算時,我們常常會遇到一些令人困惑的結果。例如,當我們將 0.1
和 0.2
相加時,結果可能不是我們預期的 0.3
。這是因為浮點數在電腦中是使用二進製表示的,而二進位制不能準確地表示所有的十進位制數字。
以下是一個示例程式,展示了浮點數運算的陷阱:
fn main() {
let abc = (0.1f32, 0.2f32, 0.3f32);
let xyz = (0.1f64, 0.2f64, 0.3f64);
assert!(abc.0 + abc.1 == abc.2);
assert!(xyz.0 + xyz.1 == xyz.2);
}
這個程式會因為 assert!
宏而當機,因為 0.1 + 0.2
的結果不是 0.3
。
使用 epsilon 值
為瞭解決這個問題,我們可以使用 epsilon 值來比較浮點數。epsilon 值是浮點數的最小單位,代表了兩個浮點數之間的最大差異。Rust 提供了 f32::EPSILON
和 f64::EPSILON
常數來表示這個值。
以下是一個示例程式,展示瞭如何使用 epsilon 值來比較浮點數:
fn main() {
let result: f32 = 0.1 + 0.1;
let desired: f32 = 0.2;
let absolute_difference = (desired - result).abs();
assert!(absolute_difference <= f32::EPSILON);
}
這個程式會檢查 result
和 desired
之間的絕對差異是否小於或等於 f32::EPSILON
。如果是,則 assert!
宏會透過,否則會當機。
處理 NaN 值
NaN(Not a Number)值是浮點數的一種特殊值,代表了無效或未定義的結果。例如,當我們嘗試計算負數的平方根時,結果就是 NaN。
NaN 值會 “汙染” 其他數字,幾乎所有與 NaN 相關的運算都會傳回 NaN。另外,NaN 值永遠不相等,即使它們看起來相同。
以下是一個示例程式,展示了 NaN 值的行為:
fn main() {
let x = (-42.0_f32).sqrt();
assert_eq!(x, x);
}
這個程式會因為 assert_eq!
宏而當機,因為 x
是 NaN 值,而 NaN 值永遠不相等。
圖表翻譯:
graph LR A[浮點數運算] --> B[結果] B --> C[檢查 epsilon 值] C --> D[透過] --> E[繼續運算] C --> F[失敗] --> G[當機] B --> H[NaN 值] H --> I[汙染其他數字] I --> J[傳回 NaN]
這個圖表展示了浮點數運算的流程,包括檢查 epsilon 值和處理 NaN 值。
2.3.4 有理數、複數和其他數值型別
Rust 的標準函式庫相對較為精簡,沒有包含其他語言中常見的數值型別。這些型別包括:
- 有理數和複數的數學物件
- 任意大小的整數和任意精確度浮點數,用於處理非常大或非常小的數字
- 固定點十進位制數,用於處理貨幣
要存取這些專門的數值型別,可以使用 num
函式庫。函式庫是 Rust 的一個包管理系統,Cargo 就是從這裡下載 num
的。
以下程式碼示範瞭如何將兩個複數相加。如果您不熟悉複數的概念,複數是一種二維數,與日常生活中的一維數不同。複數有「實部」和「虛部」,並以 <實部> + <虛部>i
的形式表示。
例如,2.1 + -1.2i
就是一個複數。
以下是推薦的編譯和執行程式碼的工作流程:
- 執行以下命令:
cd rust-in-action/ch2/ch2-complex
cargo run
- 對於那些喜歡透過實踐學習的讀者,可以按照以下步驟實作相同的結果:
- 執行以下命令:
cargo new ch2-complex
cd ch2-complex
- 在
Cargo.toml
中新增num
函式庫的版本 0.4:
- 執行以下命令:
[dependencies] num = “0.4”
* 將 `src/main.rs` 替換為以下程式碼:
```rust
use num::complex::Complex;
fn main() {
let a = Complex { re: 2.1, im: -1.2 };
let b = Complex::new(11.1, 22.2);
let result = a + b;
println!("{} + {}i", result.re, result.im)
}
* 執行 `cargo run` 命令
在執行 cargo run
命令後,應該會輸出以下結果:
13.2 + 21.02i
程式碼解釋
- 我們首先匯入
num
函式庫中的Complex
型別。 - 在
main
函式中,我們建立兩個複數a
和b
,並計算它們的和result
。 - 最後,我們使用
println!
宏將結果輸出到控制檯。
深入剖析 Rust 的基礎語法和數值型別後,我們可以發現,Rust 強調型別安全和記憶體安全,即使在處理基礎的數值運算時也如此。從變數宣告、型別註解到函式呼叫,Rust 的設計都體現了其嚴謹的風格。透過多維比較分析,我們可以看到 Rust 與其他系統級程式語言相比,在處理數值型別時更注重安全性,例如強制型別轉換和 try_into()
方法的使用,有效避免了潛在的錯誤。然而,Rust 的嚴格性也帶來了一定的複雜度,例如處理浮點數時需要考慮精確度問題和 NaN 值。技術堆疊的各層級協同運作中體現了 Rust 的設計哲學:在編譯時就儘可能地排除錯誤,從而提高程式碼的可靠性和安全性。對於追求效能和安全的開發者而言,Rust 提供了一種兼顧兩者的解決方案。從技術演進角度,Rust 對數值型別的處理方式代表了現代程式語言發展的一個重要方向,值得深入學習和應用。玄貓認為,Rust 的學習曲線雖然較陡峭,但其提供的安全性和效能優勢使其成為值得投資的技術,尤其在系統程式設計和嵌入式開發領域。對於重視長期穩定性的團隊,建議逐步引入 Rust,並著重於理解其型別系統和錯誤處理機制,以充分發揮其潛力。