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 中,變數預設是不可變的。
  • abcd 是變數名稱,它們被賦予了不同的整數值。
  • 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 的整數型別包括 i8i16i32i64i128,分別代表 8 位元、16 位元、32 位元、64 位元和 128 位元的整數。

let a: i32 = 10; // 32 位元整數
let b: i64 = 20; // 64 位元整數

浮點數

浮點數是有小數點的數字,例如 3.14 或 -0.5。Rust 的浮點數型別包括 f32f64,分別代表 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):可以代表負數和正數,例如 i8i16i32i64
  • 無號整數(u):只能代表正數,但可以達到比有號整數更高的值,例如 u8u16u32u64
  • 浮點數(f):可以代表實數,包括特殊的位元模式來表示無窮大、負無窮大和非數值,例如 f32f64

數字比較

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);
}

這個程式碼定義了兩個變數 ab,然後使用比較運算來比較它們之間的關係。最後,它使用 println! 宏來輸出比較結果。

內容解密:

在這個範例程式碼中,我們使用了 Rust 的數字型別和比較運算來比較兩個變數之間的關係。比較運算的結果會被輸出到螢幕上。這個程式碼示範瞭如何使用 Rust 的基本資料型別和運運算元來寫出簡單的程式碼。

圖表翻譯:

以下是這個範例程式碼的流程圖:

  graph LR
    A[定義變數 a 和 b] --> B[比較 a 和 b]
    B --> C[輸出比較結果]
    C --> D[結束程式]

這個流程圖示範了這個範例程式碼的執行流程。首先,我們定義了兩個變數 ab,然後比較它們之間的關係,最後輸出比較結果。

數值比較與轉換

在 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()方法被用於處理成功值。如果轉換失敗,則會傳回錯誤值。

浮點數危險

浮點數型別(如f32f64)可能會導致嚴重的錯誤。浮點數型別通常使用二進製表示法,但我們經常需要計算十進位制數字。這種差異可能會導致精確度問題。

內容解密:

在這個章節中,我們學習瞭如何使用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.20.3 之間的差值是否小於一個小的誤差值 epsilon。如果差值小於 epsilon,則認為兩個浮點數相等。

內容解密
  • 浮點數的精確度問題:浮點數不能準確地代表所有十進位制數字。
  • 比較浮點數:比較浮點數可能會因為精確度問題而失敗。
  • 使用誤差值比較浮點數:使用一個小的誤差值來比較浮點數,可以解決精確度問題。

圖表翻譯

  flowchart TD
    A[浮點數] --> B[精確度問題]
    B --> C[比較失敗]
    C --> D[使用誤差值]
    D --> E[比較成功]

這個流程圖展示了浮點數的精確度問題如何導致比較失敗,以及如何使用誤差值來解決這個問題。

浮點數運算的陷阱

在進行浮點數運算時,我們常常會遇到一些令人困惑的結果。例如,當我們將 0.10.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::EPSILONf64::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);
}

這個程式會檢查 resultdesired 之間的絕對差異是否小於或等於 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 就是一個複數。

以下是推薦的編譯和執行程式碼的工作流程:

  1. 執行以下命令:
    • cd rust-in-action/ch2/ch2-complex
    • cargo run
  2. 對於那些喜歡透過實踐學習的讀者,可以按照以下步驟實作相同的結果:
    • 執行以下命令:
      • 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 函式中,我們建立兩個複數 ab,並計算它們的和 result
  • 最後,我們使用 println! 宏將結果輸出到控制檯。

深入剖析 Rust 的基礎語法和數值型別後,我們可以發現,Rust 強調型別安全和記憶體安全,即使在處理基礎的數值運算時也如此。從變數宣告、型別註解到函式呼叫,Rust 的設計都體現了其嚴謹的風格。透過多維比較分析,我們可以看到 Rust 與其他系統級程式語言相比,在處理數值型別時更注重安全性,例如強制型別轉換和 try_into() 方法的使用,有效避免了潛在的錯誤。然而,Rust 的嚴格性也帶來了一定的複雜度,例如處理浮點數時需要考慮精確度問題和 NaN 值。技術堆疊的各層級協同運作中體現了 Rust 的設計哲學:在編譯時就儘可能地排除錯誤,從而提高程式碼的可靠性和安全性。對於追求效能和安全的開發者而言,Rust 提供了一種兼顧兩者的解決方案。從技術演進角度,Rust 對數值型別的處理方式代表了現代程式語言發展的一個重要方向,值得深入學習和應用。玄貓認為,Rust 的學習曲線雖然較陡峭,但其提供的安全性和效能優勢使其成為值得投資的技術,尤其在系統程式設計和嵌入式開發領域。對於重視長期穩定性的團隊,建議逐步引入 Rust,並著重於理解其型別系統和錯誤處理機制,以充分發揮其潛力。