文章目錄
- 變量與可變性
- 常量
- 遮蔽(Shadowing)
變量與可變性
Rust中變量默認是不可變的,這是 Rust 鼓勵你編寫更安全、易于并發代碼的眾多方式之一。不過,你仍然可以選擇讓變量可變。讓我們來探討 Rust 為什么鼓勵你優先使用不可變性,以及為什么有時你可能需要選擇可變性。
當變量是不可變的時,一旦一個值被綁定到名字上,你就無法更改該值。為說明這一點,請在你的項目目錄下用 cargo new variables
創建一個新項目。
然后,在新建的 variables 目錄下,打開 src/main.rs,并將其代碼替換為以下內容(此代碼暫時無法編譯):
文件名:src/main.rs
此代碼編譯不通過!
fn main() {let x = 5;println!("The value of x is: {x}");x = 6;println!("The value of x is: {x}");
}
保存并使用 cargo run
運行程序。你會收到關于不可變性錯誤的提示,如下所示:
$ cargo runCompiling variables v0.1.0 (file:///projects/variables)
error[E0384]: cannot assign twice to immutable variable `x`--> src/main.rs:4:5|
2 | let x = 5;| - first assignment to `x`
3 | println!("The value of x is: {x}");
4 | x = 6;| ^^^^^ cannot assign twice to immutable variable|
help: consider making this binding mutable|
2 | let mut x = 5;| +++
有關此錯誤的更多信息,請嘗試 rustc --explain E0384
。
error: could not compile variables
(bin “variables”) due to 1 previous error
這個例子展示了編譯器如何幫助你發現程序中的錯誤。
你收到“不能對不可變變量 x
進行二次賦值”的錯誤,是因為你試圖給不可變變量 x
賦第二個值。
當我們試圖更改被指定為不可變的值時,在編譯時報錯是很有價值的,因為不應改變值的變量發生了改變很容易導致 bug。如果代碼的一部分要求某個值永遠不變,而另一部分代碼卻改變了這個值,那么第一部分代碼可能就無法按預期工作。尤其是當第二部分代碼只在某些情況下才改變值時,這種 bug 很難追蹤。Rust 編譯器保證你聲明值不會改變時,它真的不會改變,因此你無需自己跟蹤。這樣你的代碼更易于理解。
但可變性有時非常有用,也能讓代碼更方便編寫。雖然變量默認是不可變的,但我們可以在變量名前加上 mut
使其可變。加上 mut
也向未來的代碼閱讀者傳達了意圖,表明代碼的其他部分會更改該變量的值。
例如,讓我們將 src/main.rs 改為如下內容:
文件名:src/main.rs
fn main() {let mut x = 5;println!("The value of x is: {x}");x = 6;println!("The value of x is: {x}");
}
現在運行程序,輸出如下:
$ cargo runCompiling variables v0.1.0 (file:///projects/variables)Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30sRunning `target/debug/variables`
The value of x is: 5
The value of x is: 6
使用 mut
后,我們可以將 x 的值從 5 改為 6。最終,是否使用可變性取決于我們實際的需求和應用場景。
常量
與不可變變量類似,常量也是綁定到名字上的值,且不允許更改,但常量和變量之間有一些區別。
首先,常量不能使用 mut
。常量不僅默認不可變——它們始終不可變。我們需要用 const
關鍵字聲明常量,而不是 let
,并且必須標注值的類型。
常量可以在任何作用域聲明,包括全局作用域,從而可供多個模塊或代碼片使用。
最后一個區別是,常量只能被賦值為常量表達式,而非在運行時計算的值。
下面是一個常量聲明的例子:
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
常量名為 THREE_HOURS_IN_SECONDS,其值為 60(每分鐘的秒數)乘以 60(每小時的分鐘數)再乘以 3(我們想要計算的小時數)。Rust 的常量命名約定是全大寫并用下劃線分隔單詞。編譯器能在編譯時完成有限的運算,這讓我們可以用更易理解的方式寫出這個值,而不是直接寫 10,800。更多關于常量聲明時可用操作的信息,請參閱 Rust Reference 的常量求值部分。
在聲明的作用域內,常量于整個程序運行期間都有效。此屬性使得常量可以作為多處代碼使用的全局范圍的值,例如一個游戲中所有玩家可以獲取的最高分或者光速。
將程序中用到的硬編碼值聲明為常量,能幫助后來的代碼維護人員了解值的意圖。如果將來需要更改硬編碼值,也只需改動一處即可。
遮蔽(Shadowing)
正如你在第 2 章的猜數字游戲教程中看到的,你可以用相同的名字聲明新變量。Rustacean 稱第一個變量被第二個變量“遮蔽”,即編譯器在使用變量名時會看到第二個變量。實際上,第二個變量覆蓋了第一個變量,直到它自己被遮蔽或作用域結束。我們可以通過重復使用 let
關鍵字和相同變量名來遮蔽變量,如下所示:
文件名:src/main.rs
fn main() {let x = 5;let x = x + 1;{let x = x * 2;println!("The value of x in the inner scope is: {x}");}println!("The value of x is: {x}");
}
該程序首先將 x 綁定為 5。然后通過 let x = x + 1;
創建了一個新變量 x,此時 x 的值為 6。接著,在用花括號創建的內部作用域中,第三個 let
語句再次遮蔽 x,創建了一個新變量,其值為前一個值乘以 2,即 12。當該作用域結束后,內部遮蔽結束,x 恢復為 6。運行該程序,輸出如下:
$ cargo runCompiling variables v0.1.0 (file:///projects/variables)Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31sRunning `target/debug/variables`
The value of x in the inner scope is: 12
The value of x is: 6
遮蔽與將變量標記為 mut
不同,因為如果你試圖在沒有使用 let
關鍵字的情況下重新賦值,會得到編譯時錯誤。通過使用 let
,我們可以對值進行多次轉換,但在這些轉換完成后變量仍然是不可變的。
mut
和遮蔽的另一個區別是,使用 let
關鍵字實際上創建了一個新變量,因此我們可以更改值的類型,但仍然復用相同的名字。例如,假設我們的程序讓用戶輸入文本之間要有多少個空格,用戶輸入空格字符后,我們想把這個輸入存儲為數字:
let spaces = " ";
let spaces = spaces.len();
第一個 spaces 變量是字符串類型,第二個 spaces 變量是數字類型。遮蔽讓我們無需起不同的名字(如 spaces_str 和 spaces_num),而可以復用更簡單的 spaces 名字。但如果我們嘗試用 mut
,如下所示,會得到編譯時錯誤:
此代碼無法編譯!
let mut spaces = " ";
spaces = spaces.len();
錯誤提示我們不能更改變量的類型:
$ cargo runCompiling variables v0.1.0 (file:///projects/variables)
error[E0308]: mismatched types--> src/main.rs:3:14|
2 | let mut spaces = " ";| ----- expected due to this value
3 | spaces = spaces.len();| ^^^^^^^^^^^^ expected `&str`, found `usize`
有關此錯誤的更多信息,請嘗試 rustc --explain E0308
。
error: could not compile variables
(bin “variables”) due to 1 previous error
整體代碼示例如下:
fn main() {// 變量可變性 let mut x = 5;println!("The value of x is: {x}");x = 6;println!("The value of x is: {x}");// 常量const MAX_POINTS: u32 = 100_000;println!("The maximum points is: {MAX_POINTS}");// 變量遮蔽let x = 5;let x = x + 1;{let x = x * 2;println!("The value of x in the inner scope is: {x}");}println!("The value of x in the outer scope is: {x}");
}
運行結果如下
The value of x is: 5
The value of x is: 6
The maximum points is: 100000
The value of x in the inner scope is: 12
The value of x in the outer scope is: 6
現在我們已經了解了變量的工作方式,接下來讓我們看看它們可以擁有的數據類型。