Rust 的靜態型別系統在編譯時檢查所有變數的型別,確保型別安全。Rust 的型別推斷機制可以減少程式碼冗餘,同時保持型別安全。泛型函式則提供高度的程式碼複用性,適用於多種資料型別。Rust 的核心型別包括整數、浮點數、布林、字元和元組,它們構成了 Rust 程式碼的基本組成部分。理解這些型別對於編寫高效且安全的 Rust 程式碼至關重要,例如,在使用整數型別時,需要注意不同整數型別的範圍和轉換規則,避免溢位錯誤。在使用浮點數型別時,則需要注意浮點數的精確度和特殊值,如 NaN 和無窮大。

Rust 的靜態型態系統

Rust 是一種靜態型別語言,這意味著在不實際執行程式的情況下,編譯器會檢查每條可能的執行路徑,以確保值的使用方式與其型別一致。這使得 Rust 能夠在早期捕捉到許多程式設計錯誤,這對於 Rust 的安全性保證至關重要。

相較於像 JavaScript 或 Python 這樣的動態型別語言,Rust 需要你在前期進行更多的規劃:你必須明確指出函式的引數和傳回值型別、結構體型別的成員等。然而,Rust 的兩個特性使得這變得不那麼麻煩:

  • 給定你已經明確指出的型別,Rust 可以推斷出大部分其他型別。在實踐中,對於一個給定的變數或表示式,往往只有一種型別是可行的;當這種情況發生時,Rust 允許你省略型別。例如,你可以明確指出函式中的每個型別,像這樣:
fn build_vector() -> Vec<i16> {
    let mut v: Vec<i16> = Vec::<i16>::new();
    v.push(10i16);
    v.push(20i16);
    v
}

但這顯得冗長且重複。根據函式的傳回型別,很明顯 v 必須是 Vec<i16>,即一個 16 位元有符號整數的向量;沒有其他型別是合適的。由此可推斷,向量的每個元素都必須是 i16

這正是 Rust 的型別推斷所採用的推理方式,從而允許你改寫成:

fn build_vector() -> Vec<i16> {
    let mut v = Vec::new();
    v.push(10);
    v.push(20);
    v
}

內容解密:

  1. let mut v = Vec::new();:Rust 的型別推斷機制根據 build_vector 函式的傳回型別 Vec<i16> 自動推斷出 v 的型別為 Vec<i16>,因此無需明確指定型別。
  2. v.push(10);v.push(20);:同樣地,Rust 根據 v 的型別推斷出 1020 應該是 i16 型別,因此可以直接寫成 1020 而不需要加上型別字尾(如 10i16)。
  3. 這兩個版本的定義是完全等價的;Rust 將生成相同的機器碼。型別推斷使得程式碼更具可讀性,同時仍然在編譯時捕捉型別錯誤。
  • 函式可以是泛型的:當一個函式的目的和實作足夠通用時,你可以定義它以適用於任何滿足必要條件的型別集合。單一定義可以涵蓋無數的使用場景。

在 Python 和 JavaScript 中,所有函式自然都是這種方式運作的:一個函式可以操作任何具有該函式所需屬性和方法的物件。(這通常被稱為鴨子型別:如果它像鴨子一樣嘎嘎叫,那麼它就是鴨子。)但正是這種靈活性使得這些語言很難在早期檢測到型別錯誤;測試往往是捕捉這種錯誤的唯一方法。

Rust 的泛型函式在保持相同靈活性的同時,仍然能在編譯時捕捉所有型別錯誤。

儘管具有靈活性,泛型函式與非泛型函式一樣高效。我們將在第 11 章詳細討論泛型函式。

本章的其餘部分將從底層開始介紹 Rust 的型別,首先介紹像整數和浮點數這樣的簡單機器型別,然後展示如何將它們組合成更複雜的結構。在適當的地方,我們將描述 Rust 如何在記憶體中表示這些型別的值,以及其效能特徵。

以下是 Rust 中常見的幾種資料型態:

型別描述範例
i8, i16, i32, i64, u8, u16, u32, u64有符號和無符號整數,具有給定的位寬42、-5i8、0x400u16、0o100i16、20_922_789_888_000u64、b’*’(u8 位元組字面值)
isize, usize有符號和無符號整數,其大小與機器上的位址大小相同(32 或 64 位元)137、-0b0101_0010isize、0xffff_fc00usize
f32, f64IEEE 浮點數,單精確度和雙精確度1.61803、3.14f32、6.0221e23f64
bool布林值true、false
charUnicode 字元,寬度為 32 位元‘*’、’\n’、‘字’、’\x7f’、’\u{CA0}’
(char, u8, i32)元組:允許混合型別(’%’, 0x7f, -1)
()“單位”元組(空元組)()

機器型別

Rust 型別系統的基礎是一組固定寬度的數值型別,這些型別被選定以符合幾乎所有現代處理器直接在硬體中實作的型別,以及布林和字元型別。

Rust 的數值型別名稱遵循一個規則模式,拼出其位寬以及所使用的表示法:

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title Rust 靜態型別系統與核心型別綜述

package "安全架構" {
    package "網路安全" {
        component [防火牆] as firewall
        component [WAF] as waf
        component [DDoS 防護] as ddos
    }

    package "身份認證" {
        component [OAuth 2.0] as oauth
        component [JWT Token] as jwt
        component [MFA] as mfa
    }

    package "資料安全" {
        component [加密傳輸 TLS] as tls
        component [資料加密] as encrypt
        component [金鑰管理] as kms
    }

    package "監控審計" {
        component [日誌收集] as log
        component [威脅偵測] as threat
        component [合規審計] as audit
    }
}

firewall --> waf : 過濾流量
waf --> oauth : 驗證身份
oauth --> jwt : 簽發憑證
jwt --> tls : 加密傳輸
tls --> encrypt : 資料保護
log --> threat : 異常分析
threat --> audit : 報告生成

@enduml

此圖示展示了 Rust 中的基本數值型別分類別,包括有符號整數、無符號整數和浮點數。它們分別對應不同的位寬和表示法,用於滿足不同的計算需求。

內容解密:

  1. 有符號整數 (i8, i16, i32, i64):表示具有正負值的整數,用於需要處理負數的計算場景。
  2. 無符號整數 (u8, u16, u32, u64):表示非負整數,用於需要正整數或自然數的場合,如陣列索引。
  3. 浮點數 (f32, f64):表示帶有小數部分的實數,用於科學計算和圖形處理等需要高精確度運算的場景。

機器型別

在程式設計中,瞭解不同資料型別的表示方式和限制至關重要。Rust 語言提供了多種整數型別,包括無符號整數、有符號整數和浮點數,每種型別都有其特定的用途和表示範圍。

整數型別

Rust 的整數型別分為無符號整數和有符號整數。無符號整數用於表示非負整數,而有符號整數則用於表示包括負數在內的整數。

無符號整數

無符號整數型別使用其全部範圍來表示正值和零。下表列出了 Rust 中無符號整數型別的範圍:

型別範圍
u80 至 2^8 - 1(0 至 255)
u160 至 2^16 - 1(0 至 65,535)
u320 至 2^32 - 1(0 至 4,294,967,295)
u640 至 2^64 - 1(0 至 18,446,744,073,709,551,615)
usize0 至 2^32 - 1 或 2^64 - 1,取決於目標機器的位址空間大小

有符號整數

有符號整數型別使用二補數表示法來表示正值和負值。下表列出了 Rust 中有符號整數型別的範圍:

型別範圍
i8-2^7 至 2^7 - 1(-128 至 127)
i16-2^15 至 2^15 - 1(-32,768 至 32,767)
i32-2^31 至 2^31 - 1(-2,147,483,648 至 2,147,483,647)
i64-2^63 至 2^63 - 1(-9,223,372,036,854,775,808 至 9,223,372,036,854,775,807)
isize-2^31 至 2^31 - 1 或 -2^63 至 2^63 - 1,取決於目標機器的位址空間大小

整數字面值

Rust 中的整數字面值可以帶有型別字尾,例如 42u8 表示一個 u8 型別的值。如果省略字尾,Rust 將嘗試根據上下文推斷型別。如果有多種可能的型別,Rust 將預設為 i32

整數字面值的表示法

Rust 支援多種整數字面值表示法,包括十進位制、十六進位制、八進位制和二進位制。

let decimal = 116i8;   // 十進位制
let hex = 0xcafeu32;  // 十六進位制
let octal = 0o106;    // 八進位制
let binary = 0b0010_1010; // 二進位制

位元組字面值

Rust 提供位元組字面值,用於表示 u8 型別的值。位元組字面值使用 b 字首,例如 b'A' 表示 ASCII 碼為 65 的字元。

let byte_literal = b'A'; // 等同於 65u8

對於某些特殊字元,需要使用跳脫序列:

let single_quote = b'\''; // 等同於 39u8
let backslash = b'\\';   // 等同於 92u8
let newline = b'\n';     // 等同於 10u8

也可以使用十六進製表示法:

let escape = b'\x1b'; // 等同於 27u8

型別轉換

可以使用 as 運算元進行整數型別之間的轉換。

assert_eq!(10_i8 as u16, 10_u16);
assert_eq!(2525_u16 as i16, 2525_i16);
assert_eq!(-1_i16 as i32, -1_i32);

當轉換超出目標型別的範圍時,將進行截斷。

assert_eq!(1000_i16 as u8, 232_u8);
assert_eq!(65535_u32 as i16, -1_i16);

整數方法

Rust 的整數型別提供了許多有用的方法,例如指數運算、絕對值和位元運算。

assert_eq!(2u16.pow(4), 16);
assert_eq!((-4i32).abs(), 4);
assert_eq!(0b101101u8.count_ones(), 4);

這些方法可以在 Rust 的標準函式庫檔案中找到。請注意,在實際程式碼中,通常可以根據上下文推斷出型別,因此不需要明確指定型別字尾。

浮點數型別(Floating-Point Types)

Rust 提供遵循 IEEE 754-2008 規範的單精確度和雙精確度浮點數型別,分別為 f32f64。這兩種型別包括正負無窮大、正負零和非數字值(NaN)。

浮點數型別的精確度與範圍

型別精確度範圍
f32IEEE 單精確度(至少 6 位十進位數字)約 -3.4 × 10³⁸ 至 +3.4 × 10³⁸
f64IEEE 雙精確度(至少 15 位十進位數字)約 -1.8 × 10³⁰⁸ 至 +1.8 × 10³⁰⁸

Rust 的 f32f64 分別對應於支援 IEEE 浮點數運算的 C 和 C++ 中的 floatdouble 型別,以及始終使用 IEEE 浮點數運算的 Java。

浮點數字面值

浮點數字面值的一般形式如下圖所示。

每個浮點數字面值在整數部分之後的部分都是可選的,但至少需要有小數部分、指數或型別字尾中的一個,以區別於整數字面值。小數部分可以僅包含一個小數點,因此 5. 是有效的浮點數常數。

浮點數字面值範例

字面值型別數學值
-1.5625推斷型別−(1 + 9/16)
2.推斷型別2
0.25推斷型別¼
1e4推斷型別10,000
40f32f3240
9.109_383_56e-31f64f64約 9.10938356 × 10⁻³¹

如果浮點數字面值沒有指定型別字尾,Rust 會根據上下文推斷其型別,預設為 f64。Rust 在型別推斷時會將整數和浮點數字面值視為不同的類別,不會將整數字面值推斷為浮點數型別,反之亦然。

浮點數的標準函式庫支援

標準函式庫中的 std::f32std::f64 模組定義了 IEEE 要求的特殊值,如 INFINITYNEG_INFINITY(負無窮大)、NAN(非數字值)、MINMAX(最大和最小有限值)。std::f32::constsstd::f64::consts 模組提供了各種常用的數學常數,如 EPI 和 2 的平方根。

使用浮點數方法進行數學計算

assert_eq!(5f32.sqrt() * 5f32.sqrt(), 5.); // IEEE 精確結果為 5.0
assert_eq!(-1.01f64.floor(), -1.0);
assert!((-1. / std::f32::INFINITY).is_sign_negative());

通常不需要在程式碼中明確指定浮點數字面值的型別字尾,因為上下文會決定其型別。但在某些情況下,錯誤訊息可能會令人困惑。例如,以下程式碼無法編譯:

println!("{}", (2.0).sqrt());

Rust 編譯器會報錯:

error: no method named `sqrt` found for type `{float}` in the current scope

解決方法是明確指定所需的型別:

println!("{}", (2.0_f64).sqrt());
println!("{}", f64::sqrt(2.0));

布林型別(The bool Type)

Rust 的布林型別 bool 有兩個值:truefalse。比較運算元(如 ==<)會產生 bool 結果。例如,2 < 5 的值為 true

許多語言在需要布林值的上下文中可以接受其他型別的值,但 Rust 非常嚴格:控制結構(如 ifwhile)要求條件必須是 bool 表示式,短路邏輯運算元(如 &&||)也是如此。必須明確寫出條件表示式,如 if x != 0 { ... },而不能簡寫為 if x { ... }

布林值與整數型別的轉換

Rust 的 as 運算元可以將 bool 值轉換為整數型別:

assert_eq!(false as i32, 0);
assert_eq!(true as i32, 1);

但不能將整數型別轉換為 bool。必須寫出明確的比較表示式,如 x != 0

字元型別(Characters)

Rust 的字元型別 char 表示單個 Unicode 字元,佔用 32 位元。Rust 對單個字元使用 char,但對字串和文字流使用 UTF-8 編碼。因此,字串表示為 UTF-8 位元組序列,而不是字元陣列。

字元字面值

字元字面值是用單引號括起來的字元,如 '8''!'。可以使用任何 Unicode 字元,例如 '錆' 代表日語漢字「sabī」(rust)。

如同位元組字面值,需要對某些字元使用反斜線轉義:

字元Rust 字元字面值
單引號,’‘'’
反斜線,\‘\’
換行符號‘\n’
回車符號‘\r’
Tab 符號‘\t’

也可以使用 Unicode 碼位以十六進位表示字元:

  • 如果字元的 Unicode 碼位在 U+0000 至 U+007F 範圍內(即 ASCII 字元集),則可以寫成 ‘\xHH’,其中 HH 是兩位十六進位數字。例如…(略)

字元與元組型別

在 Rust 程式語言中,char 型別代表一個單一的 Unicode 字元。與其他語言不同,Rust 的 char 並非單純代表一個位元組(byte),而是一個完整的 Unicode 字碼點(code point)。這意味著一個 char 可能佔用多個位元組的記憶體空間,具體取決於該字元的 Unicode 編碼。

字元表示法

Rust 提供了多種方式來表示字元:

  1. 直接使用單引號括起來的字元,如 'a'
  2. 使用 Unicode 碼點的十六進製表示法,如 '\u{CA0}',這裡的 CA0 是字元 的 Unicode 碼點。

值得注意的是,char 型別的值必須是有效的 Unicode 碼點,且不能是代理對(surrogate pair)的一半或超出 Unicode 碼空間的範圍。

字元與整數型別的轉換

Rust 不會隱式地在 char 和其他型別之間進行轉換。不過,可以使用 as 運算元將 char 轉換為整數型別。對於小於 32 位的整數型別,高位將被截斷。

assert_eq!('*' as i32, 42);
assert_eq!('ಠ' as u16, 0xca0);
assert_eq!('ಠ' as i8, -0x60); // U+0CA0 截斷為 8 位有符號整數

反之,只有 u8 可以直接使用 as 運算元轉換為 char。其他整數型別需要使用 std::char::from_u32 函式進行轉換,因為它們可能包含無效的 Unicode 碼點。

assert_eq!(std::char::from_u32(0xCA0), Some('ಠ'));
assert_eq!(std::char::from_u32(0x110000), None); // 超出 Unicode 範圍

元組(Tuples)

元組是一種可以包含多個不同型別值的資料結構。元組使用括號和逗號來定義,例如 ("Brazil", 1985) 是一個包含靜態字串和整數的元組,其型別為 (&str, i32)

存取元組元素

可以透過 . 運算元和元素的索引來存取元組的元素,例如 t.0t.1 等。

let t = ("Brazil", 1985);
assert_eq!(t.0, "Brazil");
assert_eq!(t.1, 1985);

元組的應用

Rust 經常使用元組來從函式傳回多個值。例如,字串切片的 split_at 方法會傳回一個包含兩個字串切片的元組。

let text = "I see the eigenvalue in thine eye";
let (head, tail) = text.split_at(21);
assert_eq!(head, "I see the eigenvalue ");
assert_eq!(tail, "in thine eye");

此外,元組也常用作簡易的結構體(struct)型別,尤其是在需要傳遞多個相關的值時。

fn write_image(filename: &str, pixels: &[u8], bounds: (usize, usize)) -> Result<(), std::io::Error> {
    // ...
}

這裡,bounds 引數是一個包含寬度和高度的元組。

空元組(Unit Type)

Rust 中的空元組 () 稱為單元型別(unit type),它只有一個值,即 ()。單元型別用於表示沒有有意義的傳回值,但上下文需要某種型別。

例如,不傳回任何值的函式,其傳回型別就是 ()。標準函式庫中的 std::mem::swap 函式交換兩個引數的值,但沒有有意義的傳回值,因此其傳回型別也是 ()

fn swap<T>(x: &mut T, y: &mut T) {
    // ...
}

等同於:

fn swap<T>(x: &mut T, y: &mut T) -> () {
    // ...
}

指標型別

Rust 有多種代表記憶體位址的型別,這是 Rust 與大多數具有垃圾回收機制的語言的一個重要區別。

指標型別的使用

在 Rust 中,指標型別用於直接操作記憶體位址。這些型別包括參考(references)、原始指標(raw pointers)等。正確使用指標型別對於寫出高效且安全的 Rust 程式碼至關重要。