# [為你自己學 Rust] 資料型態（原始型別 - 陣列、元組）

> 介紹 Rust 語言中的複合型資料型別，包括陣列和元組的特性。Rust 陣列要求元素型別一致，並且大小固定，這與 JavaScript 的陣列有顯著差異。透過範例程式碼，我們將說明如何有效利用陣列的優勢，並探討其限制及應用場景，幫助您更好地理解 Rust 的資料結構。

Published: 2023-09-20
URL: https://kaochenlong.com/primitive-data-types-part-3

---

前面章節介紹了有純量型（Scalar）的資料型別，這個章節來看看複合型（Compound）的資料型別。複合型主要有陣列（array）跟元組（tuple）這兩種。

也許各位看到陣列會覺得「啊這個我知道，就是用一個中括號...」，基本上是沒錯啦，但 Rust 的陣列會跟你平常在 JavaScript 裡用的陣列不太一樣。

## 陣列（Array）

如果要你簡單描述什麼是陣列，你大概會說陣列就是連續的資料，可以新增、修改或刪除裡面的資料，然後可以透過索引值（Index）取用指定的資料。在 JavaScript 我們可能寫過這樣的程式：

```javascript
const list = [ 1450, &quot;Hello&quot;, [], &quot;A&quot;];

console.log(list.length) // 總共 4 個元素
console.log(list[1])     // 印出 &quot;Hello&quot;

list.push({ name: &#39;華安&#39;, num: 9527 })  // 加入新元素

console.log(list)
// 印出 [ 1450, &#39;Hello&#39;, [], &#39;A&#39;, { name: &#39;華安&#39;, num: 9527 } ]
```

這應該沒什麼問題，但在 Rust 裡面的陣列就不是這樣用了...

### 只能放同樣性質的東西

在 JavaScript、Python 或 Ruby 裡的陣列，你想放什麼就放什麼，而且可以數字、字串混著放。但在 Rust 裡的陣列只能規定放相同型別的東西：

```rust
let list: [u8; 3] = [1, 2, 3];
println!(&quot;{:?}&quot;, list);
```

這裡我宣告了一個名為 `list` 的陣列，其中 `[u8; 3]` 的就是表示這個陣列有 3 個格子，然後格子裡面都是 `u8` 型別的資料。`u8` 在前面有介紹過，所以如果放的值超過 `u8` 的上限的話會出問題。因為有規定這些格子都只能放 `u8`，所以如果這樣寫：

```rust
let list: [u8; 3] = [&#39;a&#39;, 2, 3];
```

一執行 Rust 編譯器馬上就會跟你抱怨型別不對（mismatched types），而且無法執行：

```shell
$ cargo run
error[E0308]: mismatched types
  |
2 |     let list: [u8; 3] = [&#39;a&#39;, 2, 3];
  |                          ^^^ expected `u8`, found `char`
  |
help: if you meant to write a byte literal, prefix with `b`
  |
2 |     let list: [u8; 3] = [b&#39;a&#39;, 2, 3];
  |                          ~~~~
```

如果覺得寫 `[u8; 3]` 太麻煩，也可以讓 Rust 編譯器幫你猜：

```rust
let list = [1, 2, 3];
```

這樣就會幫你宣告一個 `[i32; 3]` 的陣列。

### 格子數量是固定的，而且要放好放滿！

Rust 的陣列宣告好之後，大小就是固定的，不能改，所以在 Rust 裡面你不能對陣列進行新增或刪除的行為，改倒是可以改，但需要加上 `mut` 的宣告：

```rust
let mut list = [1450, 9527, 5566];
list[2] = 500;

println!(&quot;{:?}&quot;, list);
```

`mut` 我們會在下個章節跟大家說明。

就是因為陣列宣告好之後不能動態的加入元素，所以一開始就要把值放好放滿，如果多放或少放都會出錯：

```rust
let list: [i32; 3] = [9527, 5566];  // 故意少放一個
println!(&quot;{:?}&quot;, list);
```

馬上就會抱怨給你看：

```shell
$ cargo run
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
  |               |     |
  |               |     help: consider specifying the actual array length: `2`
  |               expected due to this
```

### 放同樣的型別有什麼好處？

到這裡，會不會感覺陣列規定要放同樣的型別這件事有點麻煩，宣告了數字陣列就只能放數字，不能像以前一樣想放什麼就放什麼。

事實上，當在 Rust 宣告了一個陣列之後，Rust 會去要一塊連續的記憶體來放指定的資料。例如我宣告了一個 `[u8; 6]` 的陣列，你可以想像就是要去跟記憶體要 6 個空位，每個格子的寬度是 8 位元：

&lt;img src=&quot;/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBaEFDIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--8a19040b5f32e0873e1616812e671fd9480242c7/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCem9MWm05eWJXRjBTU0lJY0c1bkJqb0dSVlE2RkhKbGMybDZaVjkwYjE5c2FXMXBkRnNIYVFJQUJXa0NBQVE9IiwiZXhwIjpudWxsLCJwdXIiOiJ2YXJpYXRpb24ifX0=--578d6799c87a604ca574298502ba874c9075e929/image-20230920155741563.png&quot; alt=&quot;為你自己學 Rust&quot; width=&quot;75%&quot;&gt;

大家可能已經知道陣列是透過索引值在拿資料的，但可能還不知道拿資料的細節是什麼。Rust 一開始去要這塊記憶體的時候就會知道這一連串的記憶體的「起點」在哪裡，假設我想要取得這個陣列的第 3 個格子的資料，我只要提供索引值 `2`，同時也知道每一個格子的寬度是 8 位元，接著只要透過像是「起點 + 2 x 8 bit」簡單加法跟乘法，一下子就能找到第 3 格位置的記憶體位置：

&lt;img src=&quot;/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBaEVDIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--6aa1cd1376919b941a996d0400217ba6a46e8236/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCem9MWm05eWJXRjBTU0lJY0c1bkJqb0dSVlE2RkhKbGMybDZaVjkwYjE5c2FXMXBkRnNIYVFJQUJXa0NBQVE9IiwiZXhwIjpudWxsLCJwdXIiOiJ2YXJpYXRpb24ifX0=--578d6799c87a604ca574298502ba874c9075e929/image-20230920155756035.png&quot; alt=&quot;為你自己學 Rust&quot; width=&quot;75%&quot;&gt;

這樣存取資料的效能非常好。Rust 其實也不是第一個這樣做的，只是平常大家可能被 JavaScript 給慣壞了，不會想這麼多細節，反正畫面會動就好。

### 陣列操作

跟其它程式語言的陣列操作差不多，直接透過索引值就能取用內容：

```rust
let list = [1450, 9527, 5566];
println!(&quot;{}&quot;, list.len()); // 印出 3
println!(&quot;{}&quot;, list[1]);    // 印出 9527
```

在 JavaScript 如果用 `list[100]` 這種明顯超過應該有的索引值的寫法，只會默默的得到一個 `undefined` 而已（這設計其實有點糟糕），但如果在 Rust 裡這樣做，就會給你一個超過邊界的 Panic：

```shell
$ cargo run
error: this operation will panic at runtime
  |
4 |     println!(&quot;{}&quot;, list[100]);
  |                    ^^^^^^^^^ index out of bounds: the length is 3 but the index is 100
```

與 JavaScript 選擇妥協相比，我更喜歡 Rust 這種有錯就直說而且還會跟你說哪裡有錯的設計。

如果想要印出陣列裡的每個值，以往大概會用 `for` 迴圈搭配索引值的寫法一個一個印出來，在 Rust 的 `for` 迴圈寫起來比較像是迭代器（Iterator），寫起來像是這樣：

```rust
let list = [1450, 9527, 5566];

for item in list.iter() {
    println!(&quot;{}&quot;, item);
}
```

透過 `for...in` 的寫法可以把陣列裡的元素一個一個拿出來。其中因為 `list` 本身是陣列，所以需要再透過 `.iter()` 方法把它轉成 Iterator，才能透過 `for ... in` 處理。

另外，Rust 也有跟別人借來了「解構（destructuring）」的寫法，像這樣：

```rust
let list = [1450, 9527, 5566];
let [_, b, c] = list;
```

這樣就可以直接把變數 `b` 跟 `c` 的值設定成 `9527` 跟 `5566`，前面那個 `_` 表示「不重要、不在乎」的意思。

### 陣列感覺很不實用？

在一般開發者的觀念中，陣列就是宣告要來放東西的，放同樣型別這件事可能還可以理解，但元素的個數不能調整就會讓人覺得那我要這陣列幹嘛？

事實上還是可以的，只是那個東西不叫陣列，在 Rust 裡這樣的東西叫做「向量（Vector）」，它用起來跟各位平常在用的陣列比較接近，請待我在後續的章節再跟大家說明。

## 元組（Tuple）

先不管這是什麼東西，首先，大家覺得 Tuple 這個英文字該怎麼發音？因為在程式語言 Python 裡也有 Tuple 這個資料結構，所以曾經有人在 [Twitter](https://twitter.com/gvanrossum/status/86144775731941376) 上問 Python 的設計者 Guido 這個字該怎麼唸，結果他很風趣的回答道：

&lt;img src=&quot;/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBaElDIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--f6de0ca1f7bea4f0511af5668c25f7b8654f4d2f/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCem9MWm05eWJXRjBTU0lJY0c1bkJqb0dSVlE2RkhKbGMybDZaVjkwYjE5c2FXMXBkRnNIYVFJQUJXa0NBQVE9IiwiZXhwIjpudWxsLCJwdXIiOiJ2YXJpYXRpb24ifX0=--578d6799c87a604ca574298502ba874c9075e929/image-20230920170632077.png&quot; alt=&quot;為你自己學 Rust&quot; width=&quot;75%&quot;&gt;

他星期一、三、五唸「吐波」，星期二、四、六唸「塔波」，星期日就不唸它。事實上你想怎麼唸就怎麼唸，只要你自己或你的同事聽的懂就好。至於中文要翻譯成「元組」或「數組」就隨你開心，本書將會使用 Tuple 原文。

參考資料：https://twitter.com/gvanrossum/status/86144775731941376

Tuple 是一種資料結構，在 Rust 裡可以透過小括號來定義：

```rust
let point: (i32, i32, i32) = (100, 200, 300);
```

跟陣列不同，Tuple 裡的元素不一定需要相同型別：

```rust
let answer: (char, bool) = (&#39;蛤&#39;, false);
```

同樣，如果不想寫型別宣告，也是能交給 Rust 編譯器猜：

```rust
let pet = (&#39;🐈&#39;, false);
```

### 操作

陣列可以透過索引值存取元素的值，在 Tuple 也是差不多，只是寫起來會有點不太一樣，是透過小數點的方式：

```rust
let pet = (&#39;🐈&#39;, false, 18);
println!(&quot;{} {} {}&quot;, pet.0, pet.1, pet.2)
```

Tuple 透過看起來像索引值的「欄位（field）」來存取資料，我知道它寫起來有點怪。跟陣列一樣，如果超過應該有的範例，例如 `pet.5` ，Rust 就會說沒有這個欄位：

```shell
$ cargo run
error[E0609]: no field `5` on type `(char, bool, {integer})`
  |
3 |     println!(&quot;{} {} {}&quot;, pet.5, pet.1, pet.2)
```

另外，類似陣列的解構，Tuple 也行：

```rust
let point = (100, 200, 300);
let (x, y, z) = point;
```

Tuple 的元素數量沒有規定要幾個，要 1 個、5 個、10 個或 100 個都可以，只是通常不會用到那麼大一包，比較常在函數的回傳值上看到它。

不過有個比較特別的情況，就是空的 Tuple，它有個特別的名字叫「單元（Unit）」。

### 單元（Unit）

單元不就是一個空的 Tuple，這有什麼好特別拿出來講的，甚至還特別給它一個名字？而且，Tuple 跟陣列一樣，宣告了元素個數之後就不能改變，所以要這空的 Tuple 到底能幹嘛？

我們到現在都還沒開始寫到函數，目前都只有在進入點的 `main` 函數裡練拳腳而已。通常函數都有回傳值，Rust 需要知道所有的變數、函數的型態，所以如果函數沒有回傳值也要明確的講沒有回傳值，有些程式語言會使用 `void` 的表示法，表示這個函數沒有回傳值的意思。

照 Rust 的設計，進入點的這個 `main` 函數照是沒有也不應該有回傳值的，在 Rust 裡面要用來表示沒有回傳值的寫法，就是回傳一個 Unit：

```rust
fn main() -&gt; () {
    println!(&quot;Hello Rust&quot;)
}
```

但這邊可以省略 Unit 不寫就只是 Rust 給的一個糖衣而已。也就是說，如果某個函數說「我的回傳值是一個 Unit」，就是表示「這個函數是沒有回傳值」的。

