複製型別
Rust 中的一些型別非常簡單。它們被稱為複製型別。這些簡單型別都在堆疊上,編譯器知道它們的大小。這意味著它們非常容易複製,所以當你把它傳送到函式時,編譯器永遠會用複製的方式。它永遠會複製,是因為它們如此的小而容易到沒有理由不複製。所以你不需要擔心這些型別的所有權問題。
這些簡單的型別包括:整數、浮點數、布林值(true
和 false
)和 char
。
如何知道一個型別是否實作複製?(實作 = 能夠使用)你可以檢查文件。例如,這是 char 的文件:
https://doc.rust-lang.org/std/primitive.char.html
在左邊你可以看到 Trait Implementations。例如你可以看到 Copy, Debug, 和 Display。所以你知道 char
型別:
- 當傳送到函式時就被複制了 (Copy)
- 可以用
{}
列印 (Display) - 可以用
{:?}
列印 (Debug)
fn prints_number(number: i32) { // 沒有 -> 所以不回傳任何東西 // 如果數字不是複製型別,它會拿走資料 // 我們也不能再拿來用 println!("{}", number); } fn main() { let my_number = 8; prints_number(my_number); // 印出 8。prints_number 得到 my_number 的拷貝 prints_number(my_number); // 又印出 8。 // 沒問題,因為 my_number 是複製型別! }
但是如果你有看到 String 的文件,它不是複製型別。
https://doc.rust-lang.org/std/string/struct.String.html
在左邊的 Trait Implementations 中,你可以按字母順序查詢。A、B、C......在 C 裡面沒有 Copy,但是有 Clone。Clone 和 Copy 類似,但通常需要更多的記憶體。另外,你必須用 .clone()
來呼叫它──它不會為自己克隆(clone)。
在這個範例中,prints_country()
印出國家名稱,是個 String
。我們想印兩次,但沒辦法:
fn prints_country(country_name: String) { println!("{}", country_name); } fn main() { let country = String::from("Kiribati"); prints_country(country); prints_country(country); // ⚠️ }
但現在我們懂這個訊息了。
error[E0382]: use of moved value: `country`
--> src\main.rs:4:20
|
2 | let country = String::from("Kiribati");
| ------- move occurs because `country` has type `std::string::String`, which does not implement the `Copy` trait
3 | prints_country(country);
| ------- value moved here
4 | prints_country(country);
| ^^^^^^^ value used here after move
重點是 which does not implement the Copy trait
。但在文件中我們看到 String 實現了 Clone
特徵。所以我們可以把 .clone()
加到我們的程式碼中。這樣就建立了一個克隆,然後我們將克隆傳送到函式中。現在 country
還活著,所以我們可以使用它。
fn prints_country(country_name: String) { println!("{}", country_name); } fn main() { let country = String::from("Kiribati"); prints_country(country.clone()); // 做一個克隆並傳遞給函式。只有克隆送進去,且 country 仍然還活著 prints_country(country); }
如果 String
非常大,當然 .clone()
就會佔用很多記憶體。一個 String
可以是一整本書的長度,每次我們呼叫 .clone()
都會複製這本書。所以這時如果可以用 &
來做參考的話會比較快。例如,這段程式碼將 &str
推送到 String
上,然後每次被使用在函式時都會做一個克隆:
fn get_length(input: String) { // 接收String的所有權 println!("It's {} words long.", input.split_whitespace().count()); // 分開算字數 } fn main() { let mut my_string = String::new(); for _ in 0..50 { my_string.push_str("Here are some more words "); // 推送字句 get_length(my_string.clone()); // 每次給它一份克隆 } }
印出:
It's 5 words long.
It's 10 words long.
...
It's 250 words long.
這樣是 50 次克隆。這裡用參考代替更好:
fn get_length(input: &String) { println!("It's {} words long.", input.split_whitespace().count()); } fn main() { let mut my_string = String::new(); for _ in 0..50 { my_string.push_str("Here are some more words "); get_length(&my_string); } }
0 次克隆,而不是 50 次。
無值變數
一個沒有值的變數叫做"未初始化"變數。未初始化的意思是"還沒有開始"。它們很簡單:只需要寫上 let
和變數名:
fn main() { let my_variable; // ⚠️ }
但是你還不能使用它,如果有任何東西沒有被初始化 Rust 不會開始編譯。
但有時它們會很有用。一個好範列是:
- 當你有一個程式碼區塊,而你的變數值就在裡面,並且
- 變數需要活在程式碼區塊之外。
fn loop_then_return(mut counter: i32) -> i32 { loop { counter += 1; if counter % 50 == 0 { break; } } counter } fn main() { let my_number; { // 假裝我們需要這個程式碼區塊 let number = { // 假裝這有程式碼產生數字 // 滿滿的程式,終於: 57 }; my_number = loop_then_return(number); } println!("{}", my_number); }
印出 100
。
你可以看到 my_number
是在 main()
函式中宣告的,所以它一直活到最後。但是它的值是在迴圈裡面得到的。然而,這個值和 my_number
活得一樣長,因為 my_number
擁有這個值。而如果你在區塊裡面寫了 let my_number = loop_then_return(number)
,它就會馬上死掉。
如果你簡化程式碼,有助於想像這個概念。loop_then_return(number)
給出的結果是 100,所以我們刪除它,改寫 100
。另外,現在我們不需要 number
,所以我們也刪除它。現在它看起來像這樣:
fn main() { let my_number; { my_number = 100; } println!("{}", my_number); }
所以和說 let my_number = { 100 };
差不多。
另外注意,my_number
不是 mut
。我們在給它 50 之前並沒有給它一個值,所以它的值不曾改變過。最後,my_number
的真正程式碼只是 let my_number = 100;
。