ch03-05-control-flow.mdcommit 4284e160715917a768d25265daf2db897c683065
根據(jù)條件是否為真來決定是否執(zhí)行某些代碼,以及根據(jù)條件是否為真來重復(fù)運(yùn)行一段代碼的能力是大部分編程語言的基本組成部分。Rust 代碼中最常見的用來控制執(zhí)行流的結(jié)構(gòu)是 ?if
?表達(dá)式和循環(huán)。
?if
?表達(dá)式允許根據(jù)條件執(zhí)行不同的代碼分支。你提供一個條件并表示 “如果條件滿足,運(yùn)行這段代碼;如果條件不滿足,不運(yùn)行這段代碼?!?/p>
在 projects 目錄新建一個叫做 branches 的項目,來學(xué)習(xí) ?if
?表達(dá)式。在 src/main.rs 文件中,輸入如下內(nèi)容:
文件名: src/main.rs
fn main() {
let number = 3;
if number < 5 {
println!("condition was true");
} else {
println!("condition was false");
}
}
所有的 ?if
?表達(dá)式都以 ?if
?關(guān)鍵字開頭,其后跟一個條件。在這個例子中,條件檢查變量 ?number
?的值是否小于 5。在條件為真時希望執(zhí)行的代碼塊位于緊跟條件之后的大括號中。?if
?表達(dá)式中與條件關(guān)聯(lián)的代碼塊有時被叫做 arms,就像第二章 “比較猜測的數(shù)字和秘密數(shù)字” 部分中討論到的 ?match
?表達(dá)式中的分支一樣。
也可以包含一個可選的 ?else
?表達(dá)式來提供一個在條件為假時應(yīng)當(dāng)執(zhí)行的代碼塊,這里我們就這么做了。如果不提供 ?else
?表達(dá)式并且條件為假時,程序會直接忽略 ?if
?代碼塊并繼續(xù)執(zhí)行下面的代碼。
嘗試運(yùn)行代碼,應(yīng)該能看到如下輸出:
$ cargo run
Compiling branches v0.1.0 (file:///projects/branches)
Finished dev [unoptimized + debuginfo] target(s) in 0.31s
Running `target/debug/branches`
condition was true
嘗試改變 ?number
?的值使條件為 ?false
?時看看會發(fā)生什么:
let number = 7;
再次運(yùn)行程序并查看輸出:
$ cargo run
Compiling branches v0.1.0 (file:///projects/branches)
Finished dev [unoptimized + debuginfo] target(s) in 0.31s
Running `target/debug/branches`
condition was false
另外值得注意的是代碼中的條件 必須 是 ?bool
?值。如果條件不是 ?bool
?值,我們將得到一個錯誤。例如,嘗試運(yùn)行以下代碼:
文件名: src/main.rs
fn main() { let number = 3;
if number {
println!("number was three");
}
}
這里 ?if
?條件的值是 ?3
?,Rust 拋出了一個錯誤:
$ cargo run
Compiling branches v0.1.0 (file:///projects/branches)
error[E0308]: mismatched types
--> src/main.rs:4:8
|
4 | if number {
| ^^^^^^ expected `bool`, found integer
For more information about this error, try `rustc --explain E0308`.
error: could not compile `branches` due to previous error
這個錯誤表明 Rust 期望一個 ?bool
?卻得到了一個整數(shù)。不像 Ruby 或 JavaScript 這樣的語言,Rust 并不會嘗試自動地將非布爾值轉(zhuǎn)換為布爾值。必須總是顯式地使用布爾值作為 ?if
?的條件。例如,如果想要 ?if
?代碼塊只在一個數(shù)字不等于 ?0
? 時執(zhí)行,可以把 ?if
?表達(dá)式修改成下面這樣:
文件名: src/main.rs
fn main() {
let number = 3;
if number != 0 {
println!("number was something other than zero");
}
}
運(yùn)行代碼會打印出 ?number was something other than zero
?。
可以將 ?else if
? 表達(dá)式與 ?if
?和 ?else
?組合來實現(xiàn)多重條件。例如:
文件名: src/main.rs
fn main() {
let number = 6;
if number % 4 == 0 {
println!("number is divisible by 4");
} else if number % 3 == 0 {
println!("number is divisible by 3");
} else if number % 2 == 0 {
println!("number is divisible by 2");
} else {
println!("number is not divisible by 4, 3, or 2");
}
}
這個程序有四個可能的執(zhí)行路徑。運(yùn)行后應(yīng)該能看到如下輸出:
$ cargo run
Compiling branches v0.1.0 (file:///projects/branches)
Finished dev [unoptimized + debuginfo] target(s) in 0.31s
Running `target/debug/branches`
number is divisible by 3
當(dāng)執(zhí)行這個程序時,它按順序檢查每個 ?if
?表達(dá)式并執(zhí)行第一個條件為真的代碼塊。注意即使 6 可以被 2 整除,也不會輸出 ?number is divisible by 2
?,更不會輸出 ?else
?塊中的 ?number is not divisible by 4, 3, or 2
?。原因是 Rust 只會執(zhí)行第一個條件為真的代碼塊,并且一旦它找到一個以后,甚至都不會檢查剩下的條件了。
使用過多的 ?else if
? 表達(dá)式會使代碼顯得雜亂無章,所以如果有多于一個 ?else if
? 表達(dá)式,最好重構(gòu)代碼。為此,第六章會介紹一個強(qiáng)大的 Rust 分支結(jié)構(gòu)(branching construct),叫做 ?match
?。
因為 ?if
?是一個表達(dá)式,我們可以在 ?let
?語句的右側(cè)使用它,例如在示例 3-2 中:
文件名: src/main.rs
fn main() {
let condition = true;
let number = if condition { 5 } else { 6 };
println!("The value of number is: {number}");
}
示例 3-2:將 ?if
?表達(dá)式的返回值賦給一個變量
?number
?變量將會綁定到表示 ?if
?表達(dá)式結(jié)果的值上。運(yùn)行這段代碼看看會出現(xiàn)什么:
$ cargo run
Compiling branches v0.1.0 (file:///projects/branches)
Finished dev [unoptimized + debuginfo] target(s) in 0.30s
Running `target/debug/branches`
The value of number is: 5
記住,代碼塊的值是其最后一個表達(dá)式的值,而數(shù)字本身就是一個表達(dá)式。在這個例子中,整個 ?if
?表達(dá)式的值取決于哪個代碼塊被執(zhí)行。這意味著 ?if
?的每個分支的可能的返回值都必須是相同類型;在示例 3-2 中,?if
?分支和 ?else
?分支的結(jié)果都是 ?i32
?整型。如果它們的類型不匹配,如下面這個例子,則會出現(xiàn)一個錯誤:
文件名: src/main.rs
fn main() { let condition = true;
let number = if condition { 5 } else { "six" };
println!("The value of number is: {number}");
}
當(dāng)編譯這段代碼時,會得到一個錯誤。?if
?和 ?else
?分支的值類型是不相容的,同時 Rust 也準(zhǔn)確地指出在程序中的何處發(fā)現(xiàn)的這個問題:
$ cargo run
Compiling branches v0.1.0 (file:///projects/branches)
error[E0308]: `if` and `else` have incompatible types
--> src/main.rs:4:44
|
4 | let number = if condition { 5 } else { "six" };
| - ^^^^^ expected integer, found `&str`
| |
| expected because of this
For more information about this error, try `rustc --explain E0308`.
error: could not compile `branches` due to previous error
?if
?代碼塊中的表達(dá)式返回一個整數(shù),而 ?else
?代碼塊中的表達(dá)式返回一個字符串。這不可行,因為變量必須只有一個類型。Rust 需要在編譯時就確切的知道 ?number
?變量的類型,這樣它就可以在編譯時驗證在每處使用的 ?number
?變量的類型是有效的。如果?number
?的類型僅在運(yùn)行時確定,則 Rust 無法做到這一點;且編譯器必須跟蹤每一個變量的多種假設(shè)類型,那么它就會變得更加復(fù)雜,對代碼的保證也會減少。
多次執(zhí)行同一段代碼是很常用的,Rust 為此提供了多種 循環(huán)(loops)。一個循環(huán)執(zhí)行循環(huán)體中的代碼直到結(jié)尾并緊接著回到開頭繼續(xù)執(zhí)行。為了實驗一下循環(huán),讓我們新建一個叫做 loops 的項目。
Rust 有三種循環(huán):?loop
?、?while
?和 ?for
?。我們每一個都試試。
?loop
?關(guān)鍵字告訴 Rust 一遍又一遍地執(zhí)行一段代碼直到你明確要求停止。
作為一個例子,將 loops 目錄中的 src/main.rs 文件修改為如下:
文件名: src/main.rs
fn main() {
loop {
println!("again!");
}
}
當(dāng)運(yùn)行這個程序時,我們會看到連續(xù)的反復(fù)打印 ?again!
?,直到我們手動停止程序。大部分終端都支持一個快捷鍵,ctrl-c,來終止一個陷入無限循環(huán)的程序。嘗試一下:
$ cargo run
Compiling loops v0.1.0 (file:///projects/loops)
Finished dev [unoptimized + debuginfo] target(s) in 0.29s
Running `target/debug/loops`
again!
again!
again!
again!
^Cagain!
符號 ?^C
? 代表你在這按下了ctrl-c。在 ?^C
? 之后你可能看到也可能看不到 ?again!
? ,這取決于在接收到終止信號時代碼執(zhí)行到了循環(huán)的何處。
幸運(yùn)的是,Rust 提供了一種從代碼中跳出循環(huán)的方法。可以使用 ?break
?關(guān)鍵字來告訴程序何時停止循環(huán)。回憶一下在第二章猜猜看游戲的 “猜測正確后退出” 部分使用過它來在用戶猜對數(shù)字贏得游戲后退出程序。
我們在猜謎游戲中也使用了 ?continue
?。循環(huán)中的 ?continue
?關(guān)鍵字告訴程序跳過這個循環(huán)迭代中的任何剩余代碼,并轉(zhuǎn)到下一個迭代。
?loop
?的一個用例是重試可能會失敗的操作,比如檢查線程是否完成了任務(wù)。然而你可能會需要將操作的結(jié)果傳遞給其它的代碼。如果將返回值加入你用來停止循環(huán)的 ?break
?表達(dá)式,它會被停止的循環(huán)返回:
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};
println!("The result is {result}");
}
在循環(huán)之前,我們聲明了一個名為 ?counter
?的變量并初始化為 ?0
?。接著聲明了一個名為 ?result
?來存放循環(huán)的返回值。在循環(huán)的每一次迭代中,我們將 ?counter
?變量加 ?1
?,接著檢查計數(shù)是否等于 ?10
?。當(dāng)相等時,使用 ?break
?關(guān)鍵字返回值 ?counter * 2
?。循環(huán)之后,我們通過分號結(jié)束賦值給 ?result
?的語句。最后打印出 ?result
?的值,也就是
20。
如果存在嵌套循環(huán),?break
?和 ?continue
?應(yīng)用于此時最內(nèi)層的循環(huán)。你可以選擇在一個循環(huán)上指定一個 循環(huán)標(biāo)簽(loop label),然后將標(biāo)簽與 ?break
?或 ?continue
?一起使用,使這些關(guān)鍵字應(yīng)用于已標(biāo)記的循環(huán)而不是最內(nèi)層的循環(huán)。下面是一個包含兩個嵌套循環(huán)的示例
fn main() {
let mut count = 0;
'counting_up: loop {
println!("count = {count}");
let mut remaining = 10;
loop {
println!("remaining = {remaining}");
if remaining == 9 {
break;
}
if count == 2 {
break 'counting_up;
}
remaining -= 1;
}
count += 1;
}
println!("End count = {count}");
}
外層循環(huán)有一個標(biāo)簽 ?counting_up
?,它將從 0 數(shù)到 2。沒有標(biāo)簽的內(nèi)部循環(huán)從 10 向下數(shù)到 9。第一個沒有指定標(biāo)簽的 ?break
?將只退出內(nèi)層循環(huán)。?break 'counting_up;
? 語句將退出外層循環(huán)。這個代碼打印:
$ cargo run
Compiling loops v0.1.0 (file:///projects/loops)
Finished dev [unoptimized + debuginfo] target(s) in 0.58s
Running `target/debug/loops`
count = 0
remaining = 10
remaining = 9
count = 1
remaining = 10
remaining = 9
count = 2
remaining = 10
End count = 2
在程序中計算循環(huán)的條件也很常見。當(dāng)條件為真,執(zhí)行循環(huán)。當(dāng)條件不再為真,調(diào)用 ?break
? 停止循環(huán)。這個循環(huán)類型可以通過組合 ?loop
?、?if
?、?else
?和 ?break
?來實現(xiàn);如果你喜歡的話,現(xiàn)在就可以在程序中試試。
然而,這個模式太常用了,Rust 為此內(nèi)置了一個語言結(jié)構(gòu),它被稱為 ?while
?循環(huán)。示例 3-3 使用了 ?while
?:程序循環(huán)三次,每次數(shù)字都減一。接著,在循環(huán)結(jié)束后,打印出另一個信息并退出。
文件名: src/main.rs
fn main() {
let mut number = 3;
while number != 0 {
println!("{number}!");
number -= 1;
}
println!("LIFTOFF!!!");
}
示例 3-3: 當(dāng)條件為真時,使用 ?while
?循環(huán)運(yùn)行代碼
這種結(jié)構(gòu)消除了很多使用 ?loop
?、?if
?、?else
?和 ?break
?時所必須的嵌套,這樣更加清晰。當(dāng)條件為真就執(zhí)行,否則退出循環(huán)。
可以使用 ?while
?結(jié)構(gòu)來遍歷集合中的元素,比如數(shù)組。例如,看看示例 3-4。
文件名: src/main.rs
fn main() {
let a = [10, 20, 30, 40, 50];
let mut index = 0;
while index < 5 {
println!("the value is: {}", a[index]);
index += 1;
}
}
示例 3-4:使用 ?while
?循環(huán)遍歷集合中的元素
這里,代碼對數(shù)組中的元素進(jìn)行計數(shù)。它從索引 ?0
? 開始,并接著循環(huán)直到遇到數(shù)組的最后一個索引(這時,?index < 5
? 不再為真)。運(yùn)行這段代碼會打印出數(shù)組中的每一個元素:
$ cargo run
Compiling loops v0.1.0 (file:///projects/loops)
Finished dev [unoptimized + debuginfo] target(s) in 0.32s
Running `target/debug/loops`
the value is: 10
the value is: 20
the value is: 30
the value is: 40
the value is: 50
數(shù)組中的所有五個元素都如期被打印出來。盡管 ?index
?在某一時刻會到達(dá)值 ?5
?,不過循環(huán)在其嘗試從數(shù)組獲取第六個值(會越界)之前就停止了。
但這個過程很容易出錯;如果索引長度或測試條件不正確會導(dǎo)致程序 panic。例如,如果將 ?a
? 數(shù)組的定義改為包含 4 個元素而忘記了更新條件 ?while index < 4
?,則代碼會 panic。這也使程序更慢,因為編譯器增加了運(yùn)行時代碼來對每次循環(huán)進(jìn)行條件檢查,以確定在循環(huán)的每次迭代中索引是否在數(shù)組的邊界內(nèi)。
作為更簡潔的替代方案,可以使用 ?for
?循環(huán)來對一個集合的每個元素執(zhí)行一些代碼。?for
?循環(huán)看起來如示例 3-5 所示:
文件名: src/main.rs
fn main() {
let a = [10, 20, 30, 40, 50];
for element in a {
println!("the value is: {element}");
}
}
示例 3-5:使用 ?for
?循環(huán)遍歷集合中的元素
當(dāng)運(yùn)行這段代碼時,將看到與示例 3-4 一樣的輸出。更為重要的是,我們增強(qiáng)了代碼安全性,并消除了可能由于超出數(shù)組的結(jié)尾或遍歷長度不夠而缺少一些元素而導(dǎo)致的 bug。
例如,在示例 3-4 的代碼中,如果你將 ?a
? 數(shù)組的定義改為有四個元素,但忘記將條件更新為 ?while index < 4
?,代碼將會 panic。使用 ?for
?循環(huán)的話,就不需要惦記著在改變數(shù)組元素個數(shù)時修改其他的代碼了。
?for
?循環(huán)的安全性和簡潔性使得它成為 Rust 中使用最多的循環(huán)結(jié)構(gòu)。即使是在想要循環(huán)執(zhí)行代碼特定次數(shù)時,例如示例 3-3 中使用 ?while
?循環(huán)的倒計時例子,大部分 Rustacean 也會使用 ?for
?循環(huán)。這么做的方式是使用 ?Range
?,它是標(biāo)準(zhǔn)庫提供的類型,用來生成從一個數(shù)字開始到另一個數(shù)字之前結(jié)束的所有數(shù)字的序列。
下面是一個使用 ?for
?循環(huán)來倒計時的例子,它還使用了一個我們還未講到的方法,?rev
?,用來反轉(zhuǎn) range:
文件名: src/main.rs
fn main() {
for number in (1..4).rev() {
println!("{number}!");
}
println!("LIFTOFF!!!");
}
這段代碼看起來更帥氣不是嗎?
你做到了!這是一個大章節(jié):你學(xué)習(xí)了變量、標(biāo)量和復(fù)合數(shù)據(jù)類型、函數(shù)、注釋、 ?if
?表達(dá)式和循環(huán)!如果你想要實踐本章討論的概念,嘗試構(gòu)建如下程序:
當(dāng)你準(zhǔn)備好繼續(xù)的時候,讓我們討論一個其他語言中 并不 常見的概念:所有權(quán)(ownership)。
更多建議: