複製型別

Rust 中的一些型別非常簡單。它們被稱為複製型別。這些簡單型別都在堆疊上,編譯器知道它們的大小。這意味著它們非常容易複製,所以當你把它傳送到函式時,編譯器永遠會用複製的方式。它永遠會複製,是因為它們如此的小而容易到沒有理由不複製。所以你不需要擔心這些型別的所有權問題。

這些簡單的型別包括:整數、浮點數、布林值(truefalse)和 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,但是有 CloneCloneCopy 類似,但通常需要更多的記憶體。另外,你必須用 .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;