型別
Rust 有許多型別,讓你可以處理數字、字元等等。有些型別很簡單,有些型別比較複雜,你甚至可以建立自己的型別。
原始型別
Rust 有簡單的型別,這些型別被稱為原始型別(原始 = 非常基本)。我們將從整數和 char(字元)開始。沒有包含小數點的一整個數字就是整數。整數有兩種型別:
- 有符號整數
- 無符號整數
符號是指 + (加號)與 - (減號),所以有符號整數可以是正數,也可以是負數(如 +8,-8)。但無符號整數只能是正數,因為它們沒有符號。
有符號整數是 i8、i16、i32、i64、i128 和 isize。
無符號整數是 u8、u16、u32、u64、u128 和 usize。
i 或 u 後面的數字表示該數字的位元數,所以位元數愈多的可以表示更大的數字。8 位元 = 一個位元組,所以 i8 是佔用一個位元組空間的型別,i64 是 8 個位元組,以此類推。尺寸較大的數字型別可以容納更大的數字。例如,u8 最多可以容納最大的數字是 255,但 u16 最多可以容納 65535。而 u128 最多可以容納 340282366920938463463374607431768211455。
那什麼是 isize 和 usize 呢?這表示你的電腦類型的位元數。(你的電腦裡中央處理器的位元數叫做電腦的架構)。所以在 32 位元電腦上的 isize 和 usize 就像是 i32 和 u32,64 位元電腦上的 isize 和 usize 就像是 i64 和 u64。
需要不同整數型別的原因有很多。其中之一是電腦效能:位元組數量愈少處理速度愈快。例如,數字 -10 在 i8 是 11110110,但在 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。("轉換 u8 為 char" 意味著 "假裝 u8 是char")
透過 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。所以在同一行中,我們先用 as 讓 my_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.