型別
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.