型別

Rust 有許多型別,讓你可以處理數字、字元等等。有些型別很簡單,有些型別比較複雜,你甚至可以建立自己的型別。

原始型別

YouTube 上觀看本章內容

Rust 有簡單的型別,這些型別被稱為原始型別(原始 = 非常基本)。我們將從整數和 char(字元)開始。沒有包含小數點的一整個數字就是整數。整數有兩種型別:

  • 有符號整數
  • 無符號整數

符號是指 + (加號)與 - (減號),所以有符號整數可以是正數,也可以是負數(如 +8,-8)。但無符號整數只能是正數,因為它們沒有符號。

有符號整數是 i8i16i32i64i128isize。 無符號整數是 u8u16u32u64u128usize

i 或 u 後面的數字表示該數字的位元數,所以位元數愈多的可以表示更大的數字。8 位元 = 一個位元組,所以 i8 是佔用一個位元組空間的型別,i64 是 8 個位元組,以此類推。尺寸較大的數字型別可以容納更大的數字。例如,u8 最多可以容納最大的數字是 255,但 u16 最多可以容納 65535。而 u128 最多可以容納 340282366920938463463374607431768211455。

那什麼是 isizeusize 呢?這表示你的電腦類型的位元數。(你的電腦裡中央處理器的位元數叫做電腦的架構)。所以在 32 位元電腦上的 isizeusize 就像是 i32u32,64 位元電腦上的 isizeusize 就像是 i64u64

需要不同整數型別的原因有很多。其中之一是電腦效能:位元組數量愈少處理速度愈快。例如,數字 -10 在 i811110110,但在 i128 會是11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111110110。不過這裡它還有一些其它用途:

Rust 中的字元稱做 char。每一個 char 都對應到一個數字:字母 A 對應到數字 65,而字元 (中文的"朋友")對應數字 21451。這個數字列表被稱為 "Unicode"。Unicode 給愈常用的字元使用愈小的數字,如字母 A 到 Z,數字 0 到 9,或空格等等。

fn main() {
    let first_letter = 'A';
    let space = ' '; // ' ' 裡的空白也算一個字元
    let other_language_char = 'Ꮔ'; // 感謝 Unicode,其它語言像是切羅基語 (Cherokee) 也顯示的很好
    let cat_face = '😺'; // Emojis 也算字元
}

最常用字元的對應數字少於 256,剛好可以放進 u8 裡。要記得,u8 是 0 加上到 255 的所有數字,總共 256 種。這意味著 Rust 能使用 as 關鍵字安全地將一個 u8 轉換型別(cast)char。("轉換 u8char" 意味著 "假裝 u8char")

透過 as 轉型很有用,因為 Rust 對型別非常嚴格。它總是必需知道是什麼型別,也不會讓你一起用不同的兩種型別,即使它們都是整數。舉例來說,不能這樣用:

fn main() { // main() 是 Rust 程式開始執行的地方。程式碼會放在 {} (大括號)裡

    let my_number = 100; // 我們沒有寫出整數的型別,
                         // 因此 Rust 選擇了 i32。
                         // Rust 總是給整數選擇 i32,
                         // 如果你不教它用不同型別的話。

    println!("{}", my_number as char); // ⚠️
}

編譯器給的理由是:

error[E0604]: only `u8` can be cast as `char`, not `i32`
 --> src\main.rs:3:20
  |
3 |     println!("{}", my_number as char);
  |                    ^^^^^^^^^^^^^^^^^

幸運的是,我們可以用 as 輕鬆修正這個錯誤。我們無法將 i32 轉型為 char,但我們可以將 i32 轉型為 u8。接著我們同樣可以將 u8 轉型為 char。所以在同一行中,我們先用 asmy_number 變成 u8,再變成 char。現在它就能通過編譯了:

fn main() {
    let my_number = 100;
    println!("{}", my_number as u8 as char);
}

它會印出 d 是因為它就是100對應的 char

然而,更簡單的方法是你只要告訴 Rust 說 my_number 的型別是 u8。你要像這樣做:

fn main() {
    let my_number: u8 = 100; //  更改 my_number 為 my_number: u8
    println!("{}", my_number as char);
}

所以這些是 Rust 中會有不同整數型別的兩個原因。這裡還有一個原因:usize 是 Rust 用來 索引 的型別。(索引的意思是"哪項是第一","哪項是第二"等等) usize 是最佳的索引型別,因為:

  • 索引值不能是負數,所以它需要是一個帶 u 的數字(註:指無符號數)
  • 它要可以夠大,因為有時你需要索引很多東西,但是
  • 它不能是 u64,因為 32 位元電腦無法使用 u64

所以Rust使用了 usize,這樣你的電腦就能以它能讀取到的最大整數值進行索引。

我們再來了解一下 char。你會看到 char 總是一個字元,並且使用 '' 而不是 ""

所有的 chars 都使用 4 個位元組的記憶體,因為 4 個位元組足以容納任何種類的字元:

  • 基本字母和符號通常只需要 4 個位元組中的1個:a b 1 2 + - = $ @
  • 其他字母,如德文元音變音 (Umlauts) 或重音,需要 4 個位元組中的 2 個:ä ö ü ß è é à ñ
  • 韓文、日文或中文字元需要 3 或 4 個位元組:國 안 녕

當使用字元作為字串的一部分時,字串是用每個字元所需的最少記憶體來編碼的。

我們可以自己用 .len() 來觀察這個情況。

fn main() {
    println!("Size of a char: {}", std::mem::size_of::<char>()); // 4 位元組
    println!("Size of string containing 'a': {}", "a".len()); // .len() 給出以位元組為單位的字串大小
    println!("Size of string containing 'ß': {}", "ß".len());
    println!("Size of string containing '国': {}", "国".len());
    println!("Size of string containing '𓅱': {}", "𓅱".len());
}

這個程式會印出:

Size of a char: 4
Size of string containing 'a': 1
Size of string containing 'ß': 2
Size of string containing '国': 3
Size of string containing '𓅱': 4

你可以看到 a 的大小是一個位元組,德文的 ß 是兩個位元組,日文的 是三個位元組,古埃及的 𓅱 是四個位元組。

fn main() {
    let slice = "Hello!";
    println!("Slice is {} bytes.", slice.len());
    let slice2 = "안녕!"; // 韓文的 "hi"
    println!("Slice2 is {} bytes.", slice2.len());
}

這個程式會印出:

Slice is 6 bytes.
Slice2 is 7 bytes.

slice 長 6 個字元,佔 6 個位元組,但 slice2 長 3 個字元,佔 7 個位元組。

如果 .len() 給出的是以位元組為單位的大小,那麼以字元為單位的大小呢?我們在後面會學習這些方法,但這裡你只要記得 .chars().count() 做得到這件事就可以了。.chars().count() 會將你寫的東西變成字元,然後算出有多少個。

fn main() {
    let slice = "Hello!";
    println!("Slice is {} bytes and also {} characters.", slice.len(), slice.chars().count());
    let slice2 = "안녕!";
    println!("Slice2 is {} bytes but only {} characters.", slice2.len(), slice2.chars().count());
}

這個程式會印出:

Slice is 6 bytes and also 6 characters.
Slice2 is 7 bytes but only 3 characters.