資料型態是程式語言中非常基本也非常重要的部份。我們今天就來學習 Rust 的基礎資料型態吧!

上一篇:quiz1
本系列主頁面:Rust繁中簡學!
繁體中文版 Rustlings:https://github.com/TimLai666/rustlings-zh-TW
安裝方法:00_intro

04_primitive_types(原始資料型態)

原始類型

Rust 有幾種基本類型是直接由編譯器實現的。在這一部分,我們將介紹最重要的那些。

進一步了解

README 幾乎什麼都沒說,關於 Rust 的資料型態,可以參考 繁體中文版《Rust 程式設計語言》 裡的講解。

練習一(primitive_types1.rs

// primitive_types1.rs
//
// 填寫剩下缺少代碼的行!沒有提示,沒有技巧,只是讓您習慣打字 :)

// I AM NOT DONE

fn main() {
    // 布林值 (`bool`)

    let is_morning = true;
    if is_morning {
        println!("早安!");
    }

    let is_evening = false; // 像例子一樣完成這行!或者讓它變成 false!
    if is_evening {
        println!("晚安!");
    }
}
✓ 成功運行 exercises/04_primitive_types/primitive_types1.rs!

🎉 🎉 代碼正在編譯! 🎉 🎉

輸出:
====================
早安!

====================

您可以繼續進行此練習,
或通過刪除 `I AM NOT DONE` 註釋來進入下一個練習:

 3 |  // 填寫剩下缺少代碼的行!沒有提示,沒有技巧,只是讓您習慣打字 :)
 4 |
 5 |  // I AM NOT DONE
 6 |
 7 |  fn main() {

這題什麼都沒做就通過編譯了,應該是使用 ChatGPT 翻譯的時候被它雞婆的做完了😅(GitHub 庫已修正)。

布林值的部分很簡單,以小寫 truefalse 來表示,基本上跟 JavaScript 一樣。

那就直接跳下一題吧。

練習二(primitive_types2.rs

// primitive_types2.rs
//
// 填寫剩下缺少代碼的行!沒有提示,沒有技巧,只是讓您習慣打字 :)

// I AM NOT DONE

fn main() {
    // 字符 (`char`)

    // 注意是 _單引號_,這與您經常看到的雙引號不同。
    let my_first_initial = 'C';
    if my_first_initial.is_alphabetic() {
        println!("字母!");
    } else if my_first_initial.is_numeric() {
        println!("數字!");
    } else {
        println!("既不是字母也不是數字!");
    }

    let // 像例子一樣完成這行!您最喜歡的字符是什麼?試試字母、數字、特殊字符,或者不同語言的字符,甚至是表情符號!
    if your_character.is_alphabetic() {
        println!("字母!");
    } else if your_character.is_numeric() {
        println!("數字!");
    } else {
        println!("既不是字母也不是數字!");
    }
}
! 編譯 exercises/04_primitive_types/primitive_types2.rs 失敗!請再試一次。以下是輸出:
error: expected identifier, found keyword `if`
  --> exercises/04_primitive_types/primitive_types2.rs:21:5
   |
21 |     if your_character.is_alphabetic() {
   |     ^^ expected identifier, found keyword

error: aborting due to 1 previous error

第二題一樣是打字題,只要在 let 那裡隨便設一個放字元的變數就行。

我就放 x 好了。

// primitive_types2.rs
//
// 填寫剩下缺少代碼的行!沒有提示,沒有技巧,只是讓您習慣打字 :)

// I AM NOT DONE

fn main() {
    // 字符 (`char`)

    // 注意是 _單引號_,這與您經常看到的雙引號不同。
    let my_first_initial = 'C';
    if my_first_initial.is_alphabetic() {
        println!("字母!");
    } else if my_first_initial.is_numeric() {
        println!("數字!");
    } else {
        println!("既不是字母也不是數字!");
    }

    let your_character = 'x'; // 像例子一樣完成這行!您最喜歡的字符是什麼?試試字母、數字、特殊字符,或者不同語言的字符,甚至是表情符號!
    if your_character.is_alphabetic() {
        println!("字母!");
    } else if your_character.is_numeric() {
        println!("數字!");
    } else {
        println!("既不是字母也不是數字!");
    }
}
✓ 成功運行 exercises/04_primitive_types/primitive_types2.rs!

🎉 🎉 代碼正在編譯! 🎉 🎉

輸出:
====================
字母!
字母!

====================

您可以繼續進行此練習,
或通過刪除 `I AM NOT DONE` 註釋來進入下一個練習:

 3 |  // 填寫剩下缺少代碼的行!沒有提示,沒有技巧,只是讓您習慣打字 :)
 4 |
 5 |  // I AM NOT DONE
 6 |  
 7 |  fn main() {

這裡有一點要注意!變數裡放字元必須使用單引號('),不能使用雙引號("),否則無法通過編譯。這點跟 C++ 比較像。

從程式碼中也可以看到,變數.is_alphabetic() 可以判斷一個字元是不是字母(這裡說的「字母」包括英文字母以外的文字,因為就算是英文以外的語言,他還是會判斷為 true);變數.is_numeric() 則可以判斷該字元是不是數字(這裡的「數字」實際上還是「字元」的資料型態,跟 i32 的那種數字不一樣,要注意不能搞混了)。

練習三(primitive_types3.rs

// primitive_types3.rs
//
// 創建一個包含至少 100 個元素的陣列,並填寫 ??? 處的代碼。
//
// 執行 `rustlings hint primitive_types3` 或使用 `hint` watch 子命令來獲取提示。

// I AM NOT DONE

fn main() {
    let a = ???

    if a.len() >= 100 {
        println!("哇,那是一個大陣列!");
    } else {
        println!("嗯,我早餐吃的就是這樣的陣列。");
        panic!("陣列不夠大,需要更多元素")
    }
}
! 編譯 exercises/04_primitive_types/primitive_types3.rs 失敗!請再試一次。以下是輸出:
error: expected expression, found `?`
  --> exercises/04_primitive_types/primitive_types3.rs:10:13
   |
10 |     let a = ???
   |             ^ expected expression

error: aborting due to 1 previous error

這題要創建一個陣列。我們先看看陣列是什麼:

陣列型別

另一種取得數個數值集合的方法是使用陣列。和元組不一樣的是,陣列中的每個型別必須是一樣的。和其他語言的陣列不同,Rust 的陣列是固定長度的。

我們將數值寫在陣列中的括號內,每個數值再用逗號區隔開來:

檔案名稱:src/main.rs

fn main() {
    let a = [1, 2, 3, 4, 5];
}

當你想要你的資料被配置在堆疊(stack)而不是堆積(heap)的話,使用陣列是很好的選擇(我們會在第四章討論堆疊與堆積的內容)。或者當你想確定你永遠會取得固定長度的元素時也是。所以陣列不像向量(vector)型別那麼有彈性,向量是標準函式庫提供的集合型別,類似於陣列但允許變更長度大小。如果你不確定該用陣列或向量的話,通常你應該用向量就好。第八章將會討論更多向量的細節。

不過如果你知道元素的多寡不會變的話,陣列就是個不錯的選擇。舉例來說,如果你想在程式中使用月份的話,你可能就會選擇用陣列宣告,因為永遠只會有 12 個月份:

let months = ["一月", "二月", "三月", "四月", "五月", "六月", "七月",
              "八月", "九月", "十月", "十一月", "十二月"];

要詮釋陣列型別的話,你可以在中括號寫出型別和元素個數,並用分號區隔開來,如以下所示:

let a: [i32; 5] = [1, 2, 3, 4, 5];

i32 在此是每個元素的型別,在分號後面的數字 5 指的是此陣列有五個元素。

如果你想建立的陣列中每個元素數值都一樣的話,你可以指定一個數值後加上分號,最後寫出元素個數。如以下所示:

let a = [3; 5];

陣列 a 會包含 5 個元素,然後每個元素的初始化數值均為 3。這樣寫與 let a = [3, 3, 3, 3, 3]; 的寫法一樣,但比較簡潔。

獲取陣列元素

一個陣列是被配置在堆疊上且已知固定大小的一整塊記憶體,你可以用索引來取得陣列的元素,比如:

檔案名稱:src/main.rs

fn main() {
    let a = [1, 2, 3, 4, 5];

    let first = a[0];
    let second = a[1];
}

在此範例中,變數 first 會得到數值 1,因為這是陣列索引 [0] 的數值。變數 second 則會從陣列索引 [1] 得到數值 2

重點大概是以下幾點:

  • 陣列可以用 let 變數: [資料型態; 長度] = [元素]let 變數 = [初始值; 長度] 來宣告。
  • 陣列用中括號([])表示,陣列裡每個元素用逗號隔開。
  • Rust 陣列中的所有元素資料型態必須相同。
  • Rust 的陣列長度固定,不能改變。

看起來非常難用呢!我們回到練習吧。

繼續練習三(primitive_types3.rs

我們直接宣告一個長度為 101 、元素的值為 0 陣列。

// primitive_types3.rs
//
// 創建一個包含至少 100 個元素的陣列,並填寫 ??? 處的代碼。
//
// 執行 `rustlings hint primitive_types3` 或使用 `hint` watch 子命令來獲取提示。

// I AM NOT DONE

fn main() {
    let a = [0; 101];

    if a.len() >= 100 {
        println!("哇,那是一個大陣列!");
    } else {
        println!("嗯,我早餐吃的就是這樣的陣列。");
        panic!("陣列不夠大,需要更多元素")
    }
}
✓ 成功運行 exercises/04_primitive_types/primitive_types3.rs!

🎉 🎉 代碼正在編譯! 🎉 🎉

輸出:
====================
哇,那是一個大陣列!

====================

您可以繼續進行此練習,
或通過刪除 `I AM NOT DONE` 註釋來進入下一個練習:

 5 |  // 執行 `rustlings hint primitive_types3` 或使用 `hint` watch 子命令來獲取提示。
 6 |
 7 |  // I AM NOT DONE
 8 |
 9 |  fn main() {

練習四(primitive_types4.rs

// primitive_types4.rs
//
// 從陣列 a 中獲取一個切片,填寫 ??? 處的代碼,使測試通過。
//
// 執行 `rustlings hint primitive_types4` 或使用 `hint` watch 子命令來獲取提示。

// I AM NOT DONE

#[test]
fn slice_out_of_array() {
    let a = [1, 2, 3, 4, 5];

    let nice_slice = ???

    assert_eq!([2, 3, 4], nice_slice)
}
! 編譯 exercises/04_primitive_types/primitive_types4.rs 失敗!請再試一次。以下是輸出:
error: expected expression, found `?`
  --> exercises/04_primitive_types/primitive_types4.rs:13:22
   |
13 |     let nice_slice = ???
   |                      ^ expected expression

error: aborting due to 1 previous error

這題要從陣列中取出一個元素為 2, 3 , 4「切片」。切片的寫法是 &陣列名稱[開始(含)的索引..結束(不含)的索引]

由於索引是從 0 開始計算,所以這題應該要寫成 &a[1..4]

// primitive_types4.rs
//
// 從陣列 a 中獲取一個切片,填寫 ??? 處的代碼,使測試通過。
//
// 執行 `rustlings hint primitive_types4` 或使用 `hint` watch 子命令來獲取提示。

// I AM NOT DONE

#[test]
fn slice_out_of_array() {
    let a = [1, 2, 3, 4, 5];

    let nice_slice = &a[1..4];

    assert_eq!([2, 3, 4], nice_slice)
}
✓ 成功測試 exercises/04_primitive_types/primitive_types4.rs!

🎉 🎉 代碼正在編譯,並且測試通過! 🎉 🎉

您可以繼續進行此練習,
或通過刪除 `I AM NOT DONE` 註釋來進入下一個練習:

 5 |  // 執行 `rustlings hint primitive_types4` 或使用 `hint` watch 子命令來獲取提示。
 6 |  
 7 |  // I AM NOT DONE
 8 |  
 9 |  #[test]

練習五(primitive_types5.rs

// primitive_types5.rs
//
// 解構 `cat` 元組,使 println 可以正常工作。
//
// 執行 `rustlings hint primitive_types5` 或使用 `hint` watch 子命令來獲取提示。

// I AM NOT DONE

fn main() {
    let cat = ("Furry McFurson", 3.5);
    let /* 在此填寫解構模式 */ = cat;

    println!("{} is {} years old.", name, age);
}
! 編譯 exercises/04_primitive_types/primitive_types5.rs 失敗!請再試一次。以下是輸出:
error: expected pattern, found `=`
  --> exercises/04_primitive_types/primitive_types5.rs:11:24
   |
11 |     let /* 在此填寫解構模式 */ = cat;
   |                                ^ expected pattern

error: aborting due to 1 previous error

這題使用到了 元組

元組(Tuple)型別

元組是個將許多不同型別的數值合成一個複合型別的常見方法。元組擁有固定長度:一旦宣告好後,它們就無法增長或縮減。

我們建立一個元組的方法是寫一個用括號囊括起來的數值列表,每個值再用逗號分隔開來。元組的每一格都是一個獨立型別,不同數值不必是相同型別。以下範例我們也加上了型別詮釋,平時不一定要加上:

檔案名稱:src/main.rs

fn main() {
    let tup: (i32, f64, u8) = (500, 6.4, 1);
}

此變數 tup 就是整個元組,因為一個元組就被視為單一複合元素。要拿到元組中的每個獨立數值的話,我們可以用模式配對(pattern matching)來解構一個元組的數值,如以下所示:

檔案名稱:src/main.rs

fn main() {
    let tup = (500, 6.4, 1);

    let (x, y, z) = tup;

    println!("y 的數值為:{y}");
}

此程式先是建立了一個元組然後賦值給 tup,接著它用模式配對和 let 將 tup 拆成三個個別的變數 xy 和 z。這就叫做解構(destructuring),因為它將單一元組拆成了三個部分。最後程式將 y 的值印出來,也就是 6.4

我們也可以直接用句號(.)再加上數值的索引來取得元組內的元素。舉例來說:

檔案名稱:src/main.rs

fn main() {
    let x: (i32, f64, u8) = (500, 6.4, 1);

    let five_hundred = x.0;

    let six_point_four = x.1;

    let one = x.2;
}

此程式建立了元組 x,然後用它們個別的索引來存取元組的元素。和多數程式語言一樣,元組的第一個索引是 0。

沒有任何數值的元組有一種特殊的名稱叫做單元型別(Unit),其數值與型別都寫作 (),通常代表一個空的數值或空的回傳型別。表達式要是沒有回傳任何數值的話,它們就會隱式回傳單元型別。

回到練習五(primitive_types5.rs

這題的解構式只要用一對小括號,裡面放 nameage,然後等於 cat 就行。

// primitive_types5.rs
//
// 解構 `cat` 元組,使 println 可以正常工作。
//
// 執行 `rustlings hint primitive_types5` 或使用 `hint` watch 子命令來獲取提示。

// I AM NOT DONE

fn main() {
    let cat = ("Furry McFurson", 3.5);
    let (name, age) = cat;

    println!("{} is {} years old.", name, age);
}
✓ 成功運行 exercises/04_primitive_types/primitive_types5.rs!

🎉 🎉 代碼正在編譯! 🎉 🎉

輸出:
====================
Furry McFurson is 3.5 years old.

====================

您可以繼續進行此練習,
或通過刪除 `I AM NOT DONE` 註釋來進入下一個練習:

 5 |  // 執行 `rustlings hint primitive_types5` 或使用 `hint` watch 子命令來獲取提示。
 6 |
 7 |  // I AM NOT DONE
 8 |
 9 |  fn main() {

練習六(primitive_types6.rs

// primitive_types6.rs
//
// 使用元組索引來訪問 `numbers` 的第二個元素。您可以將表達式替換到 ??? 處,使測試通過。
//
// 執行 `rustlings hint primitive_types6` 或使用 `hint` watch 子命令來獲取提示。

// I AM NOT DONE

#[test]
fn indexing_tuple() {
    let numbers = (1, 2, 3);
    // 使用元組索引語法替換下面的 ???。
    let second = ???;

    assert_eq!(2, second,
        "這不是元組中的第二個數字!")
}
! 編譯 exercises/04_primitive_types/primitive_types6.rs 失敗!請再試一次。以下是輸出:
error: expected expression, found `?`
  --> exercises/04_primitive_types/primitive_types6.rs:13:18
   |
13 |     let second = ???;
   |                  ^ expected expression

error: aborting due to 1 previous error

這題一樣是在練習使用元組。

元組的索引很簡單:元組名稱.索引

// primitive_types6.rs
//
// 使用元組索引來訪問 `numbers` 的第二個元素。您可以將表達式替換到 ??? 處,使測試通過。
//
// 執行 `rustlings hint primitive_types6` 或使用 `hint` watch 子命令來獲取提示。

// I AM NOT DONE

#[test]
fn indexing_tuple() {
    let numbers = (1, 2, 3);
    // 使用元組索引語法替換下面的 ???。
    let second = numbers.1;

    assert_eq!(2, second,
        "這不是元組中的第二個數字!")
}
✓ 成功測試 exercises/04_primitive_types/primitive_types6.rs!

🎉 🎉 代碼正在編譯,並且測試通過! 🎉 🎉

您可以繼續進行此練習,
或通過刪除 `I AM NOT DONE` 註釋來進入下一個練習:

 5 |  // 執行 `rustlings hint primitive_types6` 或使用 `hint` watch 子命令來獲取提示。
 6 |  
 7 |  // I AM NOT DONE
 8 |  
 9 |  #[test]

總結

我們今天認識了 Rust 的布林值、字元、陣列、切片和元組。它們各自的重點如下:

  • 布林值
    1. 以小寫 truefalse 來表示是否為真。
  • 字元
    1. 必須使用單引號('),不能使用雙引號(")。
    2. 變數.is_alphabetic() 可以判斷一個字元是不是字母;變數.is_numeric() 可以判斷該字元是不是數字。
  • 陣列
    1. 陣列可以用 let 變數: [資料型態; 長度] = [元素]let 變數 = [初始值; 長度] 來宣告。
    2. 陣列用中括號([])表示,陣列裡每個元素用逗號隔開。
    3. Rust 陣列中的所有元素資料型態必須相同。
    4. Rust 的陣列長度固定,不能改變。
  • 切片
    1. &陣列名稱[開始(含)的索引..結束(不含)的索引]
  • 元組
    1. 元素可以是不同資料型態。
    2. 長度固定,不可改變。
    3. 可以用模式配對和 let 解構

這個單元真的是大雜燴,用到一堆重要觀念 README 卻都不寫。Rustlings 在概念解說的部分實在有待加強。

Similar Posts

One Comment

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *