原始型別 - 陣列、元組
前面章節介紹了純量型(Scalar)的資料型別,這個章節我們來看看複合型(Compound)的資料型別。複合型主要有陣列(array)跟元組(tuple)這兩種。
也許各位看到陣列會覺得「啊,陣列我知道,就是用一個中括號...」,基本上是沒錯啦,但 Rust 的陣列會跟你平常在 JavaScript 裡用的陣列不太一樣。
陣列(Array)
如果要你簡單描述什麼是陣列,你大概會說陣列就是連續的資料,可以新增、修改或刪除裡面的資料,然後可以透過索引值(Index)取用指定的資料。在 JavaScript 我們可能寫過這樣的程式:
const list = [1450, "Hello", [], "A"];
console.log(list.length); // 總共 4 個元素
console.log(list[1]); // 印出 "Hello"
list.push({ name: "華安", num: 9527 }); // 加入新元素
console.log(list);
// 印出 [ 1450, 'Hello', [], 'A', { name: '華安', num: 9527 } ]
這應該沒什麼問題,但在 Rust 裡面的陣列就不是這樣用了...
只能放同性質的東西
在 JavaScript、Python 或 Ruby 裡的陣列,你想放什麼就放什麼,而且可以數字、字串混著放。但在 Rust 裡的陣列只能規定放相同型別的東西:
let list: [u8; 3] = [1, 2, 3];
println!("{:?}", list);
這裡我宣告了一個名為 list
的陣列,其中 [u8; 3]
的就是表示這個陣列有 3 個格子,然後格子裡面都是 u8
型別的資料。u8
在前面有介紹過,所以如果放的值超過 u8
的上限的話會出問題。因為有規定這些格子都只能放 u8
,所以如果這樣寫:
let list: [u8; 3] = ['a', 2, 3];
一執行 Rust 編譯器馬上就會跟你抱怨型別不對(mismatched types),而且無法執行:
error[E0308]: mismatched types
2 | let list: [u8; 3] = ['a', 2, 3];
| ^^^ expected `u8`, found `char`
如果覺得寫 [u8; 3]
太麻煩,也可以讓 Rust 編譯器幫你猜:
let list = [1, 2, 3];
這樣就會幫你宣告一個 [i32; 3]
的陣列。
格子數量是固定的,而且要放好放滿!
Rust 的陣列宣告好之後,大小就是固定的,不能改,所以在 Rust 裡面你不能對陣列進行新增或刪除的行為,改倒是可以改,但需要加上 mut
的宣告:
let mut list = [1450, 9527, 5566];
list[2] = 500;
println!("{:?}", list);
mut
我們會在下個章節跟大家說明。
就是因為陣列宣告好之後不能動態的加入元素,所以一開始就要把值放好放滿,如果多放或少放都會出錯:
let list: [i32; 3] = [9527, 5566]; // 故意少放一個
println!("{:?}", list);
馬上就會抱怨給你看:
error[E0308]: mismatched types
2 | let list: [i32; 3] = [9527, 5566];
| -------- ^^^^^^^^^^^^ expected an array with a fixed size of 3 elements, found one with 2 elements
放同樣的型別有什麼好處?
到這裡,會不會感覺陣列規定要放同樣的型別這件事有點麻煩,宣告了數字陣列就只能放數字,不能像以前一樣想放什麼就放什麼。
事實上,當在 Rust 宣告了一個陣列之後,Rust 會去要一塊連續的記憶體來放指定的資料。例如我宣告了一個 [u8; 6]
的陣列,你可以想像就是要去跟記憶體要 6 個空位,每個格子 的寬度是 8 位元:
大家可能已經知道陣列是透過索引值在拿資料的,但可能還不知道拿資料的細節是什麼。Rust 一開始去要這塊記憶體的時候就會知道這一連串的記憶體的「起點」在哪裡,假設我想要取得這個陣列的第 3 個格子的資料,我只要提供索引值 2
,同時也知道每一個格子的寬度是 8 位元,接著只要透過像是「起點 + 2 x 8 bit」簡單加法跟乘法,一下子就能找到第 3 格位置的記憶體位置:
這樣存取資料的效能非常好。Rust 其實也不是第一個這樣做的,只是平常大家可能被 JavaScript 給慣壞了,不會想這麼多細節,反正畫面會動就好。
陣列操作
Rust 的陣列跟其它程式語言的陣列操作差不多,透過索引值就能取用內容:
let list = [1450, 9527, 5566];
println!("{}", list.len()); // 印出 3
println!("{}", list[1]); // 印出 9527
在 JavaScript 如果用 list[100]
這種明顯超過應該有的索引值的寫法,只會默默的得到一個 undefined
而已(這設計其實有點糟糕),但如果在 Rust 裡這樣做,就會給你一個超過邊界的錯誤訊息:
error: this operation will panic at runtime
4 | println!("{}", list[100]);
| ^^^^^^^^^ index out of bounds: the length is 3 but the index is 100