可變性
當你用 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
是一個字串型別。
遮蔽
遮蔽 (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 中看到的遮蔽就是這種情況。它發生在你想快速得對變數做一些事情,然後再做其他事情的地方。而你通常將它用在那些你不太關心的臨時變數上。