可變性

YouTube 上觀看本章內容

當你用 let 宣告變數時,它是不可變的(immutable,內容不可被變動)。

這個程式不能編譯:

fn main() {
    let my_number = 8;
    my_number = 10; // ⚠️
}

編譯器說:error[E0384]: cannot assign twice to immutable variable my_number。這是因為如果你只寫 let,變數是不可變的。

但有時你想更改你的變數。要建立一個可以改變的變數,就要在 let 後面加上 mut

fn main() {
    let mut my_number = 8;
    my_number = 10;
}

現在就沒問題了。

但是,你不能改變型別:即使加上 mut 也做不到。這樣將會無法編譯:

fn main() {
    let mut my_variable = 8; // 它現在是 i32. 型別不能被改變
    my_variable = "Hello, world!"; // ⚠️
}

你會看到編譯器發出的同樣的"預期"訊息。expected integer, found &str。我們很快就會知道 &str 是一個字串型別。

遮蔽

YouTube 上觀看本章內容

遮蔽 (Shadowing) 是指使用 let 宣告與另一個變數同名的新變數。它看起來像可變性,但完全不同。遮蔽看起來像這樣:

fn main() {
    let my_number = 8; // 這是 i32
    println!("{}", my_number); // 印出 8
    let my_number = 9.2; // 這是同名的 f64。 但它已經不是第一個 my_number──它完全不一樣!
    println!("{}", my_number) // 印出 9.2
}

這裡我們會說我們用一個新的 "let 繫結(binding)" 對 my_number 進行了"遮蔽"。

那麼第一個 my_number 是否被銷毀了呢?沒有,但是當我們叫用 my_number 時,我們現在得到 f64 型別的 my_number。因為它們在同一個作用域區塊中(同一個 {}),我們無法再看到第一個 my_number 了。

但如果它們在不同的區塊中,我們可以同時看到兩者。例如:

fn main() {
    let my_number = 8; // 這是 i32
    println!("{}", my_number); // 印出 8
    {
        let my_number = 9.2; // 這是 f64。 它不是原先的 my_number──它完全不一樣!
        println!("{}", my_number) // 印出 9.2
                                  // 但是被遮蔽的 my_number 只活到這裡。
                                  // 原來的 my_number 還活著!
    }
    println!("{}", my_number); // 印出 8
}

因此,當你對一個變數遮蔽時,你不會銷毀它。你阻擋了它。

那麼遮蔽的好處是什麼呢?當你需要經常改變一個變數的時候,遮蔽很好用。想象你想用變數做很多簡單數學運算時:

fn times_two(number: i32) -> i32 {
    number * 2
}

fn main() {
    let final_number = {
        let y = 10;
        let x = 9; // x 從 9 開始
        let x = times_two(x); // 遮蔽後新的 x: 18
        let x = x + y; // 遮蔽後新的 x: 28
        x // 回傳 x: final_number 現在是 x 的值
    };
    println!("The number is now: {}", final_number)
}

如果沒有遮蔽,你將要思考用什麼不同的名稱,即使你並不關心變數 x:

fn times_two(number: i32) -> i32 {
    number * 2
}

fn main() {
    // Pretending we are using Rust without 遮蔽
    let final_number = {
        let y = 10;
        let x = 9; // x 從 9 開始
        let x_twice = times_two(x); // x 的第二個名字
        let x_twice_and_y = x_twice + y; // x 的第三個名字!
        x_twice_and_y // 真糟糕沒有遮蔽可用──我們只要用 x 就好
    };
    println!("The number is now: {}", final_number)
}

一般來說,你在 Rust 中看到的遮蔽就是這種情況。它發生在你想快速得對變數做一些事情,然後再做其他事情的地方。而你通常將它用在那些你不太關心的臨時變數上。