組與元組實(shí)戰(zhàn):從類型系統(tǒng)到切片與所有權(quán))
寫過(guò)幾年 C/C 或 Java 的開發(fā)者剛接觸 Rust 時(shí)通常會(huì)有一個(gè)共同感受語(yǔ)法上好像沒(méi)多難但一寫就編譯不過(guò)。尤其是數(shù)組和元組這種最基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu)表面上是“把幾個(gè)值放在一起”真正用起來(lái)卻發(fā)現(xiàn)Rust 把“長(zhǎng)度”直接塞進(jìn)了類型系統(tǒng)里[i32; 5]和[i32; 6]是兩個(gè)完全不同的類型。這個(gè)設(shè)計(jì)讓很多從動(dòng)態(tài)語(yǔ)言或傳統(tǒng)靜態(tài)語(yǔ)言轉(zhuǎn)過(guò)來(lái)的人非常不適應(yīng)但理解它之后你反而會(huì)感謝這種嚴(yán)格。這篇文章不打算把《Rust 權(quán)威指南》里那一章翻譯一遍而是從一個(gè)實(shí)際入門者的視角把數(shù)組和元組講透它們到底是什么、怎么初始化、怎么遍歷、有哪些必須避開的坑以及為什么真正寫業(yè)務(wù)代碼時(shí)函數(shù)參數(shù)里更常見的是切片[T]而不是數(shù)組本身。文中所有示例我都會(huì)給出完整代碼和運(yùn)行結(jié)果你可以直接復(fù)制到本地 Cargo 項(xiàng)目或 Rust Playground 里驗(yàn)證。1. 為什么數(shù)組和元組值得單獨(dú)講在很多語(yǔ)言里數(shù)組就是“一串同類型的值”元組就是“一串不同類型的值”好像沒(méi)什么可講的。但在 Rust 里這兩個(gè)東西都被設(shè)計(jì)得比表面看起來(lái)更嚴(yán)格而恰恰是這種嚴(yán)格決定了你寫的代碼是優(yōu)雅還是反復(fù)被編譯器教育。先說(shuō)數(shù)組。Rust 的數(shù)組類型寫作[T; N]T是元素類型N是編譯期常量。這句話需要拆開理解數(shù)組長(zhǎng)度固定不能像Vec那樣隨意 push 或 pop。數(shù)組長(zhǎng)度是類型的一部分所以[i32; 3]和[i32; 4]不是同一個(gè)類型。數(shù)組默認(rèn)分配在棧上所以它非常快但大數(shù)組容易棧溢出。再說(shuō)元組。元組把多個(gè)不同類型的值包在一起比如(Alice, 24, 85.5)是一個(gè)(str, i32, f64)類型。它沒(méi)有名字字段訪問(wèn)方式是通過(guò)tup.0、tup.1這種位置下標(biāo)。這種設(shè)計(jì)在函數(shù)需要返回多個(gè)值時(shí)特別好用但同時(shí)也很容易讓人寫出可讀性差的代碼后面我會(huì)專門討論什么時(shí)候該用元組、什么時(shí)候該換成結(jié)構(gòu)體。所以這篇文章的核心判斷是Rust 的數(shù)組和元組不是“基礎(chǔ)語(yǔ)法這么簡(jiǎn)單”它們體現(xiàn)的是 Rust 對(duì)內(nèi)存和類型的雙重控制欲。你需要先把這種思維方式轉(zhuǎn)過(guò)來(lái)后面學(xué)所有權(quán)、借用、切片、Vec 才會(huì)順理成章。2. 環(huán)境準(zhǔn)備用一個(gè)最小 Cargo 項(xiàng)目跑通示例在開始寫代碼之前先準(zhǔn)備好實(shí)驗(yàn)環(huán)境。如果你只想快速驗(yàn)證語(yǔ)法直接用 Rust Playground 在線編譯運(yùn)行不需要安裝任何東西。如果想在本地練習(xí)我建議創(chuàng)建一個(gè)最小 Cargo 項(xiàng)目。安裝 Rust 的工具鏈一般通過(guò)rustup完成。Windows 用戶可以去官網(wǎng)下載rustup-init.exeLinux 和 macOS 用戶通常在終端執(zhí)行安裝腳本。如果你所在網(wǎng)絡(luò)下載速度不理想可以配置國(guó)內(nèi)鏡像源常見的有中科大源、字節(jié)跳動(dòng) rsproxy 源等具體配置方式以鏡像站最新文檔為準(zhǔn)。安裝完成后打開終端執(zhí)行cargo new rust_array_tuple_demo cd rust_array_tuple_demo然后打開src/main.rs把后面每一節(jié)中的代碼粘進(jìn)去運(yùn)行。執(zhí)行命令是cargo run如果main.rs里同時(shí)有多個(gè)示例建議逐個(gè)替換測(cè)試不要讓互不相關(guān)的邏輯擠在一起。下面我們正式開始。3. 數(shù)組固定長(zhǎng)度、同類型、編譯期大小3.1 數(shù)組的聲明與初始化Rust 數(shù)組的最基本形式是[T; N]。初始化方式主要有三種。fn main() { // 方式 1直接列出所有元素 let arr1: [i32; 5] [1, 2, 3, 4, 5]; // 方式 2用相同值重復(fù)填充長(zhǎng)度和類型由編譯器推斷 let arr2 [0; 5]; // 等價(jià)于 [0, 0, 0, 0, 0] // 方式 3用常量控制長(zhǎng)度 const N: usize 4; let arr3 [7; N]; println!(arr1 {:?}, arr1); println!(arr2 {:?}, arr2); println!(arr3 {:?}, arr3); }運(yùn)行結(jié)果arr1 [1, 2, 3, 4, 5] arr2 [0, 0, 0, 0, 0] arr3 [7, 7, 7, 7, 7]這里有兩個(gè)新手容易踩的坑。第一個(gè)坑數(shù)組長(zhǎng)度必須編譯期可確定。你不能這樣寫let n 5; let arr [0; n]; // 編譯錯(cuò)誤n 不是編譯期常量編譯器會(huì)明確告訴你數(shù)組長(zhǎng)度期望的是一個(gè)常量表達(dá)式。解決辦法是把n改成const N: usize 5;或者干脆使用Veclet n 5; let vec vec![0; n]; // 運(yùn)行時(shí)創(chuàng)建這是 Vec 的職責(zé)第二個(gè)坑[T; N]和[T; M]是不同類型。如果定義一個(gè)函數(shù)參數(shù)是[i32; 3]你想把一個(gè)長(zhǎng)度為 4 的數(shù)組傳進(jìn)去編譯直接報(bào)類型不匹配。這在 C/C 里不是問(wèn)題因?yàn)?C 數(shù)組在傳參時(shí)退化成指針長(zhǎng)度信息經(jīng)常丟失Rust 則把這個(gè)信息保留在了類型系統(tǒng)里。3.2 數(shù)組的訪問(wèn)與遍歷數(shù)組訪問(wèn)使用索引和大多數(shù)語(yǔ)言一樣fn main() { let arr [10, 20, 30, 40, 50]; println!(第一個(gè)元素: {}, arr[0]); println!(第三個(gè)元素: {}, arr[2]); let idx 4; println!(arr[{}] {}, idx, arr[idx]); }運(yùn)行結(jié)果第一個(gè)元素: 10 第三個(gè)元素: 30 arr[4] 50遍歷數(shù)組的方法有很多種。我會(huì)按推薦程度從低到高列舉。fn main() { let arr [10, 20, 30]; // 方式 1使用范圍索引可讀性一般還有越界風(fēng)險(xiǎn) for i in 0..arr.len() { println!(arr[{}] {}, i, arr[i]); } // 方式 2直接迭代引用推薦 for v in arr { println!(value {}, v); } // 方式 3使用 iter() 方法推薦 for v in arr.iter() { println!(value {}, v); } // 方式 4同時(shí)需要下標(biāo)和值使用 enumerate for (i, v) in arr.iter().enumerate() { println!(index {}, value {}, i, v); } }前兩種方式背后都是借用數(shù)組不會(huì)移動(dòng)數(shù)組本身。后面講所有權(quán)時(shí)你會(huì)更清楚為什么推薦arr或iter()而不是直接for v in arr。簡(jiǎn)單記憶默認(rèn)優(yōu)先用iter()需要下標(biāo)時(shí)用enumerate()。3.3 數(shù)組的邊界檢查很多老牌 C 項(xiàng)目里數(shù)組越界是一個(gè)經(jīng)典的“未定義行為”可能造成安全漏洞。Rust 在運(yùn)行時(shí)對(duì)數(shù)組訪問(wèn)做了邊界檢查越界會(huì)直接 panic而不是讓你讀到神秘內(nèi)存。fn main() { let arr [1, 2, 3]; let idx 5; println!({}, arr[idx]); }這段代碼能編譯過(guò)因?yàn)?idx 是運(yùn)行時(shí)變量但運(yùn)行時(shí)會(huì)報(bào)錯(cuò)thread main panicked at src/main.rs:4:26: index out of bounds: the len is 3 but the index is 5注意panic在 Rust 里是可捕獲的崩潰不是“繼續(xù)運(yùn)行但結(jié)果錯(cuò)誤”。這比 C 的行為安全得多但也說(shuō)明一個(gè)問(wèn)題如果你自己不提前判斷索引范圍程序就會(huì)直接崩潰。實(shí)際項(xiàng)目中更穩(wěn)妥的做法是使用get方法fn main() { let arr [1, 2, 3]; let idx 5; match arr.get(idx) { Some(v) println!(查到了: {}, v), None println!(索引 {} 越界了, idx), } }運(yùn)行結(jié)果索引 5 越界了get返回Optioni32不存在時(shí)返回None程序不會(huì)崩潰。這個(gè)模式在后面處理用戶輸入、外部數(shù)據(jù)時(shí)非常重要。3.4 多維數(shù)組Rust 的多維數(shù)組本質(zhì)上是“數(shù)組的數(shù)組”。一個(gè)[[i32; 3]; 2]類型最外層有 2 個(gè)元素每個(gè)元素是[i32; 3]。如果把它看作矩陣那就是 2 行 3 列。fn main() { // 一個(gè) 2 行 3 列的矩陣 let matrix: [[i32; 3]; 2] [ [1, 2, 3], [4, 5, 6], ]; println!(matrix[0][1] {}, matrix[0][1]); // 第一行第二列 println!(matrix[1][2] {}, matrix[1][2]); // 第二行第三列 for row in matrix { for value in row { print!({} , value); } println!(); } }運(yùn)行結(jié)果matrix[0][1] 2 matrix[1][2] 6 1 2 3 4 5 6如果你使用過(guò) C/C 的二維數(shù)組會(huì)發(fā)現(xiàn) Rust 的聲明順序其實(shí)很直觀外層數(shù)組是“行”內(nèi)層數(shù)組是“列”。容易犯錯(cuò)的地方在于如果你想用[0; 3]; 2快速初始化一個(gè) 2 行 3 列全零矩陣必須確認(rèn)寫的是[[0; 3]; 2]而不是[[0; 2]; 3]。前者是每行 3 個(gè)元素共 2 行后者是每行 2 個(gè)元素共 3 行語(yǔ)義完全不同。3.5 數(shù)組的常用方法數(shù)組雖然長(zhǎng)度固定但在方法調(diào)用上很靈活因?yàn)楹芏鄶?shù)組方法其實(shí)是自動(dòng)借用并當(dāng)作切片來(lái)調(diào)用的。常用的有這些fn main() { let arr [3, 1, 4, 1, 5, 9]; println!(長(zhǎng)度: {}, arr.len()); println!(是否為空: {}, arr.is_empty()); let first arr.first(); // Optioni32 let last arr.last(); // Optioni32 println!(first {:?}, last {:?}, first, last); // 查找元素的位置 if let Some(pos) arr.iter().position(|x| x 4) { println!(數(shù)字 4 的下標(biāo)是 {}, pos); } // 判斷是否包含某個(gè)值 println!(包含 1 嗎 {}, arr.contains(1)); // 求和注意這里得到的是 i32 let sum: i32 arr.iter().sum(); println!(總和 {}, sum); }運(yùn)行結(jié)果長(zhǎng)度: 6 是否為空: false first Some(3), last Some(9) 數(shù)字 4 的下標(biāo)是 2 包含 1 嗎 true 總和 23如果你是 C 轉(zhuǎn) Rust 的開發(fā)者可以把[T; N]類比成std::arrayT, N但 Rust 的語(yǔ)法更簡(jiǎn)潔類型系統(tǒng)對(duì)長(zhǎng)度的約束也更嚴(yán)格。4. 切片數(shù)組的“視圖”在實(shí)際 Rust 項(xiàng)目中你很少看到一個(gè)函數(shù)參數(shù)寫arr: [i32; 5]。因?yàn)檫@樣函數(shù)就只能接收長(zhǎng)度恰好為 5 的數(shù)組復(fù)用性很差。更常見的是接收一個(gè)切片arr: [i32]。切片是一個(gè)“指向一段連續(xù)內(nèi)存的視圖”它知道數(shù)據(jù)的起始位置和長(zhǎng)度但不擁有這段數(shù)據(jù)。數(shù)組可以自動(dòng)轉(zhuǎn)換成切片比如fn main() { let arr [1, 2, 3, 4, 5]; // 完整切片 let s1: [i32] arr[..]; // 部分切片 let s2: [i32] arr[1..4]; println!(s1 {:?}, s1); println!(s2 {:?}, s2); }運(yùn)行結(jié)果s1 [1, 2, 3, 4, 5] s2 [2, 3, 4]為什么要強(qiáng)調(diào)切片因?yàn)樗?Rust 中“函數(shù)間傳遞數(shù)組數(shù)據(jù)”的標(biāo)準(zhǔn)方式。看下面這個(gè)例子fn sum(values: [i32]) - i32 { let mut total 0; for v in values { total v; } total } fn main() { let arr1 [1, 2, 3]; let arr2 [10, 20, 30, 40]; println!(arr1 總和 {}, sum(arr1)); println!(arr2 總和 {}, sum(arr2)); }運(yùn)行結(jié)果arr1 總和 6 arr2 總和 100這個(gè)sum函數(shù)既能接收長(zhǎng)度 3 的數(shù)組也能接收長(zhǎng)度 4 的數(shù)組因?yàn)樗鼈兌急晦D(zhuǎn)換成了[i32]。更關(guān)鍵的是切片本身帶有長(zhǎng)度信息函數(shù)內(nèi)部可以通過(guò)values.len()獲取實(shí)際長(zhǎng)度。理解數(shù)組和切片的關(guān)系是學(xué)習(xí) Rust 所有權(quán)和借用時(shí)的一個(gè)重要節(jié)點(diǎn)。可以這樣記憶數(shù)組是“實(shí)際擁有數(shù)據(jù)”的容器分配在棧上。切片是“借用數(shù)據(jù)”的視角不擁有數(shù)據(jù)也沒(méi)有所有權(quán)。把a(bǔ)rr傳給接收[i32]的函數(shù)是安全、推薦的做法。5. 元組不同類型打包成一個(gè)復(fù)合值5.1 元組的定義與訪問(wèn)元組用圓括號(hào)定義元素之間用逗號(hào)分隔元素類型可以不同。定義一個(gè)(i32, f64, str)類型的元組fn main() { let person: (str, u8, f64) (Alice, 24, 85.5); println!(姓名: {}, person.0); println!(年齡: {}, person.1); println!(成績(jī): {}, person.2); }運(yùn)行結(jié)果姓名: Alice 年齡: 24 成績(jī): 85.5元組的訪問(wèn)方式是.0、.1、.2而不是person[0]。如果你習(xí)慣了數(shù)組下標(biāo)這一步很容易寫錯(cuò)。person[0]在編譯期就會(huì)被拒絕因?yàn)樵M不是數(shù)組沒(méi)有下標(biāo)訪問(wèn)操作。有一個(gè)特殊情況需要特別注意單元素元組。要表示一個(gè)只包含一個(gè)元素的元組必須寫成(42,)帶上那個(gè)多余的逗號(hào)。如果寫成(42)它就是一個(gè)普通的整數(shù)值類型是i32不是元組。fn main() { let single (42,); // 這是元組 let not_tuple (42); // 這只是 i32 println!(single 類型 {:?}, single); println!(not_tuple 值 {}, not_tuple); }5.2 元組的解構(gòu)元組最常見的操作之一是解構(gòu)也就是把一個(gè)元組一次性拆成多個(gè)變量。這在接收函數(shù)返回值時(shí)尤其好用。fn main() { let person (Bob, 30, 92.0); let (name, age, score) person; println!(name {}, name); println!(age {}, age); println!(score {}, score); }運(yùn)行結(jié)果name Bob age 30 score 92.0解構(gòu)時(shí)也可以用下劃線忽略不需要的部分let (_, age, _) person;這在處理返回多個(gè)值的場(chǎng)景中非常實(shí)用你只關(guān)心其中一兩個(gè)字段不必把所有變量都接出來(lái)。5.3 函數(shù)返回多個(gè)值元組的典型用途如果你用 Java 或 C# 寫多返回值通常要定義一個(gè)類或者用out參數(shù)。Rust 的做法很直接返回一個(gè)元組。下面這個(gè)函數(shù)接收一個(gè)整數(shù)切片返回最小值和最大值fn min_max(values: [i32]) - (i32, i32) { let mut min values[0]; let mut max values[0]; for v in values { if v min { min v; } if v max { max v; } } (min, max) } fn main() { let arr [3, 7, 2, 9, 5]; let (min, max) min_max(arr); println!(最小值 {}, min); println!(最大值 {}, max); }運(yùn)行結(jié)果最小值 2 最大值 9這里函數(shù)的返回類型(i32, i32)就是元組類型函數(shù)內(nèi)部直接(min, max)返回。調(diào)用方再用元組解構(gòu)同時(shí)拿到兩個(gè)值。注意上面這個(gè)函數(shù)假設(shè)傳入的切片至少有一個(gè)元素。如果values為空values[0]會(huì) panic。實(shí)際項(xiàng)目中建議增加空值判斷比如返回Option(i32, i32)后續(xù)我會(huì)在常見問(wèn)題里說(shuō)明。5.4 元組可以作為類型別名當(dāng)同一個(gè)元組類型在代碼中出現(xiàn)多次時(shí)可以使用type關(guān)鍵字起一個(gè)別名提高可讀性type Point (f64, f64); fn distance(p1: Point, p2: Point) - f64 { let dx p1.0 - p2.0; let dy p1.1 - p2.1; (dx * dx dy * dy).sqrt() } fn main() { let a: Point (0.0, 0.0); let b: Point (3.0, 4.0); println!(兩點(diǎn)距離 {}, distance(a, b)); }運(yùn)行結(jié)果兩點(diǎn)距離 5不過(guò)這里我要給一個(gè)很現(xiàn)實(shí)的建議當(dāng)元組超過(guò) 3 個(gè)字段或者某個(gè)元組在項(xiàng)目中被反復(fù)傳遞時(shí)請(qǐng)改用結(jié)構(gòu)體。原因是person.0、person.1、person.2這種寫法并沒(méi)有語(yǔ)義你看到.1不知道它代表的是年齡還是成績(jī)。結(jié)構(gòu)體的person.age比person.1可讀性高出一個(gè)數(shù)量級(jí)。6. 完整示例用數(shù)組和元組做一個(gè)成績(jī)統(tǒng)計(jì)接下來(lái)用一個(gè)綜合例子把數(shù)組、元組、切片、解構(gòu)串起來(lái)。假設(shè)班級(jí)有 5 名學(xué)生我們把每個(gè)學(xué)生的信息存成一個(gè)(學(xué)號(hào), 姓名, 成績(jī))元組再把這些元組放進(jìn)一個(gè)數(shù)組最后統(tǒng)計(jì)平均分和最高分。const STUDENT_COUNT: usize 5; fn main() { let students: [(str, str, i32); STUDENT_COUNT] [ (1001, Alice, 88), (1002, Bob, 92), (1003, Carol, 76), (1004, David, 95), (1005, Eve, 69), ]; let mut total 0; let mut max_score 0; let mut max_name ; for (id, name, score) in students { println!({}: {} 成績(jī) {}, id, name, score); total score; if score max_score { max_score score; max_name name; } } let avg total as f64 / STUDENT_COUNT as f64; println!(平均分: {:.1}, avg); println!(最高分: {} 分由 {} 取得, max_score, max_name); }運(yùn)行結(jié)果1001: Alice 成績(jī) 88 1002: Bob 成績(jī) 92 1003: Carol 成績(jī) 76 1004: David 成績(jī) 95 1005: Eve 成績(jī) 69 平均分: 84.0 最高分: 95 分由 David 取得這個(gè)例子展示了幾個(gè)關(guān)鍵點(diǎn)數(shù)組長(zhǎng)度是編譯期常量STUDENT_COUNT類型是[(str, str, i32); 5]。元組可以放在數(shù)組里作為復(fù)合元素。for (id, name, score) in students通過(guò)模式解構(gòu)遍歷每個(gè)元組。如果把數(shù)組換成切片參數(shù)函數(shù)可以寫得更通用。比如統(tǒng)計(jì)任意成績(jī)列表的平均分和最高分fn average_and_max(scores: [(str, i32)]) - (f64, str, i32) { let mut total 0; let mut max_score i32::MIN; let mut max_name ; for (name, score) in scores { total score; if *score max_score { max_score *score; max_name name; } } let avg total as f64 / scores.len() as f64; (avg, max_name, max_score) } fn main() { let students [ (Alice, 88), (Bob, 92), (Carol, 76), ]; let (avg, max_name, max_score) average_and_max(students); println!(平均分 {:.1}最高分 {} 分獲得者 {}, avg, max_score, max_name); }運(yùn)行結(jié)果平均分 85.3最高分 92 分獲得者 Bob注意這里遍歷scores時(shí)scores是[(str, i32)]每次迭代得到的是(str, i32)所以解構(gòu)時(shí)要寫作for (name, score) in scores其中name是strscore是i32因此比較時(shí)用了*score。如果你覺(jué)得麻煩也可以改成for (name, score) in scores這樣name是strscore是i32可讀性更好。7. 常見問(wèn)題與排查思路下面整理數(shù)組、元組初學(xué)者最常遇到的幾類問(wèn)題。問(wèn)題現(xiàn)象可能原因排查方式解決方案編譯錯(cuò)誤the length of the array is part of its type把[i32; 3]當(dāng)成[i32; 4]使用查看報(bào)錯(cuò)位置的數(shù)組類型標(biāo)注和傳參類型保證長(zhǎng)度一致或改用切片[i32]接收編譯錯(cuò)誤no method named push found for array數(shù)組長(zhǎng)度固定不能動(dòng)態(tài)擴(kuò)容確認(rèn)是否使用了 array 而不是 Vec改用VecT用vec![]創(chuàng)建運(yùn)行時(shí)報(bào)錯(cuò)index out of bounds訪問(wèn)了下標(biāo)越界位置檢查索引變量是否超出len()-1用get()返回Option或用iter().enumerate()編譯錯(cuò)誤mismatched types元組字段類型不一致元組類型不匹配比如(i32, i32)和(i32, f64)檢查函數(shù)簽名和實(shí)際傳入的元組統(tǒng)一字段類型或使用顯式類型標(biāo)注想訪問(wèn)元組但寫成tup[0]元組不支持下標(biāo)訪問(wèn)語(yǔ)法編譯器會(huì)提示“無(wú)法對(duì)元組進(jìn)行索引”用tup.0、tup.1訪問(wèn)編譯錯(cuò)誤n不是常量不能作為數(shù)組長(zhǎng)度數(shù)組長(zhǎng)度必須是編譯期常量檢查[0; n]的寫法寫成const N: usize 5;或改用vec![0; n]運(yùn)行main直接棧溢出創(chuàng)建了大數(shù)組數(shù)組分配在棧上查看數(shù)組元素?cái)?shù)量和大小改用Vec堆分配這些問(wèn)題的共同根源其實(shí)是同一個(gè)思維方式差異Rust 中數(shù)組的“長(zhǎng)度”不只是運(yùn)行時(shí)的屬性它在編譯期就參與類型檢查。當(dāng)你理解了這一點(diǎn)絕大多數(shù)數(shù)組相關(guān)的編譯錯(cuò)誤都能一眼看穿。8. 最佳實(shí)踐與工程建議8.1 數(shù)組、切片和 Vec 的選擇很多初學(xué)者會(huì)問(wèn)我到底什么時(shí)候用數(shù)組什么時(shí)候用 Vec我的建議是元素?cái)?shù)量在編譯期就固定并且比較小用數(shù)組。元素?cái)?shù)量需要在運(yùn)行時(shí)決定或者后續(xù)要增刪用Vec。函數(shù)需要接收一段連續(xù)同類型數(shù)據(jù)時(shí)優(yōu)先用[T]作為參數(shù)類型而不是帶著具體長(zhǎng)度的[T; N]。數(shù)組的棧分配讓它在性能上有優(yōu)勢(shì)但棧空間是有限的。一個(gè)幾十萬(wàn)元素的[u64; 500_000]直接放在 main 里很容易觸發(fā)棧溢出。這種場(chǎng)景請(qǐng)果斷使用Vec。8.2 避免裸索引訪問(wèn)能用get()就不要用arr[i]。這不是說(shuō)索引訪問(wèn)不好而是get()強(qiáng)制你處理邊界情況把“越界”變成一個(gè)普通的Option::None分支而不是讓整個(gè)程序 panic。在遍歷時(shí)也盡量用iter()、iter_mut()不要寫for i in 0..arr.len()。后者不僅啰嗦而且在你修改數(shù)組長(zhǎng)度時(shí)容易遺漏邊界。8.3 元組字段不要超過(guò) 3 個(gè)元組的tup.0、tup.1沒(méi)有語(yǔ)義信息。臨時(shí)返回兩個(gè)、三個(gè)值時(shí)用它很合適比如(min, max)、(count, sum)。但如果一個(gè)元組超過(guò) 3 個(gè)字段或者同樣的元組在多個(gè)函數(shù)間傳來(lái)傳去請(qǐng)定義結(jié)構(gòu)體struct StudentScore { id: String, name: String, score: i32, }結(jié)構(gòu)體字段有名字可讀性好也能幫編譯器做更清晰的類型檢查。8.4 函數(shù)返回多個(gè)關(guān)聯(lián)值優(yōu)先考慮返回值而不是輸出參數(shù)如果你是從 C# 或 C 轉(zhuǎn)來(lái)的可能習(xí)慣用out參數(shù)或指針參數(shù)來(lái)返回多個(gè)結(jié)果。在 Rust 里直接返回元組是更符合語(yǔ)言風(fēng)格的做法fn compute(input: i32) - (i32, i32) { (input * 2, input 1) }調(diào)用方解構(gòu)即可。這樣邏輯清晰也不會(huì)因?yàn)榻栌靡?guī)則引入不必要的復(fù)雜性。8.5 對(duì)空數(shù)據(jù)保持敏感前面min_max函數(shù)的例子已經(jīng)提到對(duì)空切片調(diào)用values[0]會(huì) panic。更健壯的版本是返回Optionfn min_max(values: [i32]) - Option(i32, i32) { if values.is_empty() { return None; } let mut min values[0]; let mut max values[0]; for v in values[1..] { min min.min(v); max max.max(v); } Some((min, max)) } fn main() { let arr [4, 1, 9, 2]; match min_max(arr) { Some((min, max)) println!(min{}, max{}, min, max), None println!(切片為空), } }運(yùn)行結(jié)果min1, max9這種Option(T, T)模式非常常見它的意思就是請(qǐng)求可能失敗失敗時(shí)返回None調(diào)用方必須處理。8.6 使用 const 定義數(shù)組長(zhǎng)度如果同一個(gè)數(shù)組長(zhǎng)度在代碼中多次出現(xiàn)建議用常量統(tǒng)一管理const MAX_POINTS: usize 100; const POINTS: [f64; MAX_POINTS] [0.0; MAX_POINTS];這里MAX_POINTS同時(shí)出現(xiàn)在類型和初始化值里只需修改一處即可。如果你在多個(gè)地方硬編碼[0.0; 100]一旦需求變化要搜遍整個(gè)項(xiàng)目才能改完。9. 總結(jié)與下一步學(xué)習(xí)方向這篇文章圍繞 Rust 的數(shù)組和元組重點(diǎn)講了三個(gè)核心認(rèn)知第一數(shù)組[T; N]的長(zhǎng)度在編譯期就是類型的一部分這意味著長(zhǎng)度不匹配的問(wèn)題會(huì)被提前攔截但同時(shí)也要求你在架構(gòu)設(shè)計(jì)時(shí)更早考慮數(shù)據(jù)結(jié)構(gòu)形態(tài)。第二真正寫通用函數(shù)時(shí)參數(shù)類型通常不是數(shù)組本身而是切片[T]它讓同一段邏輯適用于不同長(zhǎng)度的數(shù)組。第三元組是組合不同類型值的輕量方案適合函數(shù)返回多個(gè)值和臨時(shí)解構(gòu)但超過(guò) 3 個(gè)字段時(shí)應(yīng)該考慮結(jié)構(gòu)體。對(duì)于剛開始學(xué) Rust 的人來(lái)說(shuō)我的建議是不要急著去背所有權(quán)規(guī)則而是先把數(shù)組、元組、切片、Vec 這些基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)練熟。你可以把平時(shí)用 Python 或 C 寫的小練習(xí)逐個(gè)改成 Rust 版本體會(huì)它在編譯期做了什么約束。環(huán)境變量、命令行參數(shù)、文件內(nèi)容處理這些場(chǎng)景都會(huì)頻繁用到切片和元組。下一步可以按這個(gè)順序繼續(xù)深入先搞懂Vec和切片的差異再學(xué)習(xí)結(jié)構(gòu)體和枚舉然后進(jìn)入所有權(quán)與借用這個(gè) Rust 最核心的課題。數(shù)組和元組雖然只是很小的一塊拼圖但它是你建立 Rust 類型思維的第一步。建議把文章中的示例代碼復(fù)制到本地 Cargo 項(xiàng)目里逐段運(yùn)行。真正遇到報(bào)錯(cuò)時(shí)先讀編譯器信息再看代碼里的類型和長(zhǎng)度大部分問(wèn)題都能在這個(gè)思路下解決。