Rust 變量和可變性

2023-03-22 15:08 更新
ch03-01-variables-and-mutability.md
commit 54164e99f7a1ad27fc6fc578783994513abd988d

正如第二章中“使用變量?jī)?chǔ)存值” 部分提到的那樣,變量默認(rèn)是不可改變的(immutable)。這是 Rust 提供給你的眾多優(yōu)勢(shì)之一,讓你得以充分利用 Rust 提供的安全性和簡(jiǎn)單并發(fā)性來(lái)編寫代碼。不過,你仍然可以使用可變變量。讓我們探討一下 Rust 為何及如何鼓勵(lì)你利用不可變性,以及何時(shí)你會(huì)選擇不使用不可變性。

當(dāng)變量不可變時(shí),一旦值被綁定一個(gè)名稱上,你就不能改變這個(gè)值。為了對(duì)此進(jìn)行說(shuō)明,使用 cargo new variables 命令在 projects 目錄生成一個(gè)叫做 variables 的新項(xiàng)目。

接著,在新建的 variables 目錄,打開 src/main.rs 并將代碼替換為如下代碼,這些代碼還不能編譯,我們會(huì)首次檢查到不可變錯(cuò)誤(immutability error)。

文件名: 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 運(yùn)行程序。應(yīng)該會(huì)看到一條錯(cuò)誤信息,如下輸出所示:

$ cargo run
   Compiling 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`
  |         help: consider making this binding mutable: `mut x`
3 |     println!("The value of x is: {x}");
4 |     x = 6;
  |     ^^^^^ cannot assign twice to immutable variable

For more information about this error, try `rustc --explain E0384`.
error: could not compile `variables` due to previous error

這個(gè)例子展示了編譯器如何幫助你找出程序中的錯(cuò)誤。雖然編譯錯(cuò)誤令人沮喪,但那只是表示程序不能安全的完成你想讓它完成的工作;并 不能 說(shuō)明你不是一個(gè)好程序員!經(jīng)驗(yàn)豐富的 Rustacean 們一樣會(huì)遇到編譯錯(cuò)誤。

錯(cuò)誤信息指出錯(cuò)誤的原因是 不能對(duì)不可變變量 x 二次賦值cannot assign twice to immutable variable `x` ),因?yàn)槟銍L試對(duì)不可變變量 x 賦第二個(gè)值。

在嘗試改變預(yù)設(shè)為不可變的值時(shí),產(chǎn)生編譯時(shí)錯(cuò)誤是很重要的,因?yàn)檫@種情況可能導(dǎo)致 bug。如果一部分代碼假設(shè)一個(gè)值永遠(yuǎn)也不會(huì)改變,而另一部分代碼改變了這個(gè)值,第一部分代碼就有可能以不可預(yù)料的方式運(yùn)行。不得不承認(rèn)這種 bug 的起因難以跟蹤,尤其是第二部分代碼只是 有時(shí) 會(huì)改變值。

Rust 編譯器保證,如果聲明一個(gè)值不會(huì)變,它就真的不會(huì)變,所以你不必自己跟蹤它。這意味著你的代碼更易于推導(dǎo)。

不過可變性也是非常有用的,可以用來(lái)更方便地編寫代碼。盡管變量默認(rèn)是不可變的,你仍然可以在變量名前添加 mut 來(lái)使其可變,正如在第二章所做的那樣。mut 也向讀者表明了其他代碼將會(huì)改變這個(gè)變量值的意圖。

例如,讓我們將 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}");
}

現(xiàn)在運(yùn)行這個(gè)程序,出現(xiàn)如下內(nèi)容:

$ cargo run
   Compiling variables v0.1.0 (file:///projects/variables)
    Finished dev [unoptimized + debuginfo] target(s) in 0.30s
     Running `target/debug/variables`
The value of x is: 5
The value of x is: 6

通過 mut,允許把綁定到 x 的值從 5 改成 6。是否讓變量可變的最終決定權(quán)仍然在你,取決于在某個(gè)特定情況下,你是否認(rèn)為變量可變會(huì)讓代碼更加清晰明了。

常量

類似于不可變變量,常量(constants) 是綁定到一個(gè)名稱的不允許改變的值,不過常量與變量還是有一些區(qū)別。

首先,不允許對(duì)常量使用 mut。常量不光默認(rèn)不能變,它總是不能變。

聲明常量使用 const 關(guān)鍵字而不是 let,并且 必須 注明值的類型。在下一部分,“數(shù)據(jù)類型” 中會(huì)介紹類型和類型注解,現(xiàn)在無(wú)需關(guān)心這些細(xì)節(jié),記住總是標(biāo)注類型即可。

常量可以在任何作用域中聲明,包括全局作用域,這在一個(gè)值需要被很多部分的代碼用到時(shí)很有用。

最后一個(gè)區(qū)別是,常量只能被設(shè)置為常量表達(dá)式,而不可以是其他任何只能在運(yùn)行時(shí)計(jì)算出的值。

下面是一個(gè)聲明常量的例子:

const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;

常量的名稱是 THREE_HOURS_IN_SECONDS,它的值被設(shè)置為 60(一分鐘內(nèi)的秒數(shù))乘以 60(一小時(shí)內(nèi)的分鐘數(shù))再乘以 3(我們?cè)谶@個(gè)程序中要計(jì)算的小時(shí)數(shù))的結(jié)果。Rust 對(duì)常量的命名約定是在單詞之間使用全大寫加下劃線。編譯器能夠在編譯時(shí)計(jì)算一組有限的操作,這使我們可以選擇以更容易理解和驗(yàn)證的方式寫出此值,而不是將此常量設(shè)置為值10,800。有關(guān)聲明常量時(shí)可以使用哪些操作的詳細(xì)信息,請(qǐng)參閱 Rust Reference 的常量求值部分

在聲明它的作用域之中,常量在整個(gè)程序生命周期中都有效,此屬性使得常量可以作為多處代碼使用的全局范圍的值,例如一個(gè)游戲中所有玩家可以獲取的最高分或者光速。

將遍布于應(yīng)用程序中的硬編碼值聲明為常量,能幫助后來(lái)的代碼維護(hù)人員了解值的意圖。如果將來(lái)需要修改硬編碼值,也只需修改匯聚于一處的硬編碼值。

隱藏

正如在第二章猜數(shù)字游戲中所講,我們可以定義一個(gè)與之前變量同名的新變量。Rustacean 們稱之為第一個(gè)變量被第二個(gè) 隱藏(Shadowing) 了,這意味著當(dāng)您使用變量的名稱時(shí),編譯器將看到第二個(gè)變量。實(shí)際上,第二個(gè)變量“遮蔽”了第一個(gè)變量,此時(shí)任何使用該變量名的行為中都會(huì)視為是在使用第二個(gè)變量,直到第二個(gè)變量自己也被隱藏或第二個(gè)變量的作用域結(jié)束??梢杂孟嗤兞棵Q來(lái)隱藏一個(gè)變量,以及重復(fù)使用 let 關(guān)鍵字來(lái)多次隱藏,如下所示:

文件名: 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}");
}

這個(gè)程序首先將 x 綁定到值 5 上。接著通過 let x = 創(chuàng)建了一個(gè)新變量 x,獲取初始值并加 1,這樣 x 的值就變成 6 了。然后,在使用花括號(hào)創(chuàng)建的內(nèi)部作用域內(nèi),第三個(gè) let 語(yǔ)句也隱藏了 x 并創(chuàng)建了一個(gè)新的變量,將之前的值乘以 2x 得到的值是 12。當(dāng)該作用域結(jié)束時(shí),內(nèi)部 shadowing 的作用域也結(jié)束了,x 又返回到 6。運(yùn)行這個(gè)程序,它會(huì)有如下輸出:

$ cargo run
   Compiling variables v0.1.0 (file:///projects/variables)
    Finished dev [unoptimized + debuginfo] target(s) in 0.31s
     Running `target/debug/variables`
The value of x in the inner scope is: 12
The value of x is: 6

隱藏與將變量標(biāo)記為 mut 是有區(qū)別的。當(dāng)不小心嘗試對(duì)變量重新賦值時(shí),如果沒有使用 let 關(guān)鍵字,就會(huì)導(dǎo)致編譯時(shí)錯(cuò)誤。通過使用 let,我們可以用這個(gè)值進(jìn)行一些計(jì)算,不過計(jì)算完之后變量仍然是不可變的。

mut 與隱藏的另一個(gè)區(qū)別是,當(dāng)再次使用 let 時(shí),實(shí)際上創(chuàng)建了一個(gè)新變量,我們可以改變值的類型,并且復(fù)用這個(gè)名字。例如,假設(shè)程序請(qǐng)求用戶輸入空格字符來(lái)說(shuō)明希望在文本之間顯示多少個(gè)空格,接下來(lái)我們想將輸入存儲(chǔ)成數(shù)字(多少個(gè)空格):

    let spaces = "   ";
    let spaces = spaces.len();

第一個(gè) spaces 變量是字符串類型,第二個(gè) spaces 變量是數(shù)字類型。隱藏使我們不必使用不同的名字,如 spaces_str 和 spaces_num;相反,我們可以復(fù)用 spaces 這個(gè)更簡(jiǎn)單的名字。然而,如果嘗試使用 mut,將會(huì)得到一個(gè)編譯時(shí)錯(cuò)誤,如下所示:

    let mut spaces = "   ";
    spaces = spaces.len();

這個(gè)錯(cuò)誤說(shuō)明,我們不能改變變量的類型:

$ cargo run
   Compiling 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`

For more information about this error, try `rustc --explain E0308`.
error: could not compile `variables` due to previous error

現(xiàn)在我們已經(jīng)了解了變量如何工作,讓我們看看變量可以擁有的更多數(shù)據(jù)類型。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)