Rust: 果然,連流程控制都這麼另類

大家好,我是 polarisxu。

這是 Rust 勸退系列的第 7 個教程,探討 Rust 中的流程控制。注意,跟其他語言一樣,Rust 中有條件、循環,但沒有 switch,而是有 match 模式匹配。

這個系列常規的標題閱讀量實在有點看不下去,所以試試其他標題。

01 運算符

開始講解流程控制之前,先補充一個知識點,那就是 Rust 的運算符。

Rust 支持算術運算符、關係運算符、邏輯運算符和位運算符 4 種,它們和其他語言沒有什麼不同,因此不細講了,只提醒一點:Rust 中沒有自增(++)和自減運算符(--)。

Go 中 ++-- 是語句,只有一種形式:後綴,即 i++;而 C 等語言支持前綴和後綴,如 i++、++i。Rust 乾脆全沒有。

吐槽:自增和自減運算符,有時候挺好用的,Rust 爲啥不支持呢?!(難道因爲沒有常規的 for 循環,所以不需要?)

02 語句和表達式

爲什麼專門介紹語句和表達式?!

上文提到,Go 中的自增或自減是語句而不是表達式,這有什麼不同呢?

很多語言對語句和表達式基本不會特意區分、強調,所以很多人也不會在意這兩者有什麼不同。但在 Rust 中,還是很有必要區分它們的。

實際上,Rust 中的語法可以分爲兩大類:語句(Statement)和表達式(Expression)。語句是指要執行的一些操作和產生副作用的表達式;而表達式主要用於計算求值。

語句通常分爲聲明語句和表達式語句。像聲明各種語言項,如變量、常量、結構體、函數等,都是聲明語句:

let a = 1;
const PI: i32 = 3.14;

而表達式語句,指的是以分號結尾的表達式,一般會涉及到將多個表達式組合爲語句。

《Rust 編程之道》上說,Rust 中的表達式一般分爲「位置表達式」和「值表達式」,概念太多,容易勸退,直接按照其他語言的叫法:左值和右值。

之所以都用表達式的說法,是因爲 Rust 中一切皆表達式。

羅裏吧嗦講一堆,似乎沒啥用。知道有這麼回事即可。只需要記住 Rust 中一切皆表達式即可。

03 條件表達式

知道爲什麼要強調「表達式」了嗎?一般語言中都叫:條件語句,但 Rust 中卻是條件表達式。

首先,條件表達式的語法和其他語言的條件語句類似,支持 if、else if、else 等,但它和 Go 中類似,條件默認都不需要括號。但因爲是表達式,所以它有返回值,而 Rust 是強類型語言,因此返回值的類型必須確定。比如以下代碼是能正常編譯的:

fn testif() -> &'static str {
    let name = "polarisxu";
    if name == "polarisxu" {
        "Welcome"
    } else {
        "Forbidden"
    }
}

看不懂沒關係。我們只關注 if-else 部分。

在塊表達式裏(由 {} 包圍),直接一個字符串字面值(這是值表達式),連分號都沒有。Rust 沒有分號和 Go 中的沒有分號意義是不一樣的。

一切皆表達式,大括號包圍起來的是塊表達式,那塊表達式的值是什麼?它的值是裏面一系列表達式中最後一個表達式的值。

所以,上面的代碼,無論是執行到 if 還是 else,整個 if 表達式的值的類型是字符串。所以,以上代碼可以改爲這樣:

fn testif() -> &'static str {
    let name = "polarisxu";
    let result = if name == "polarisxu" {
        "Welcome"
    } else {
        "Forbidden"
    };

    hello
}

我們將 if 表達式的結果保存在 result 變量中,注意 if 表達式大括號最後的分號,這種情況,分號不能省略。

因此,在 if 表達式中,各個分支表達式最終的結果類型必須一致,否則編譯不通過。這也是爲什麼 Rust 不支持三元操作符 ?: 的原因。

if 是表達式有它的好處。在 Go 語言中,經常會寫類似這樣的代碼:

var result string
if name == "polarisxu" {
  result = "Welcome"
} else {
  result = "Forbidden"
}

而 Rust 的代碼,result 少寫了很多次。但需要注意各分支結果類型的一致性。

if 表達式可以這麼用,其他流程控制表達式也可以這麼用。

特別說明一點。如果塊表達式的最後一個表達式是語句,比如:

fn testif() -> &'static str {
    let name = "polarisxu";
    if name == "polarisxu" {
        "Welcome";
    } else {
        "forbidden";
    }
}

這時編譯會報錯:mismatched types。

因爲函數要求返回值類型是 &str,而函數體最後返回的類型是空。這個空,在其他語言中一般是沒有返回值,或者是 void。但在 Rust 中,這個空是前面介紹類型時介紹過的「unit」類型,即 (),該類型有唯一的值,也是 ()。

所以,我們可以去掉函數的返回值,或者返回 ():

fn testif() -> () {
    let name = "polarisxu";
    if name == "polarisxu" {
        "Welcome";
    } else {
        "forbidden";
    }
}

很另類,有木有?!

04 循環表達式

Rust 中包含三種循環表達式:while、loop 和 for…in。其用法和其他編程語言相應的語句類似。(注意,Go 中只有 for 一種循環語句)

loop 循環比較特殊,一般語言中沒有,它其實就是 while true {},相當於 Go 中的 for {}。不得不說,還是 Go 簡單呀!

而 while 循環,相當於 Go 中的 for condition {},condition 爲 true 時,執行循環體。

你發現沒,循環搞這麼複雜,竟然沒有其他語言中普通的 for 循環?因爲 for…in 可以搞定。

比如 Go 中的 for i := 0; i < 10; i++,在 Rust 中是這樣的:for i in 0..10 {}。來個簡單的例子,從 1 加到 100:

let mut sum = 0;
for i in 1..=100 {
  sum += i;
}
println!("1+2+..+100={}", sum);

小細節:1..10 表示範圍 [1, 10),而 1..=10 表示範圍 [1, 10]

最後,和其他語言一樣,循環支持 continue 和 break 語句。

05 小結

Rust 中一切皆表達式,當某個地方需要一個表達式,但卻是一個語句時,編譯器會自動補上單元值,即 (),這算是一個特殊的表達式。

雖然控制結構,if、循環等都是表達式,爲了不搞特殊化(畢竟大家習慣很多其他語言,特殊化可能容易把自己搞迷糊),建議大家儘量別把它們當表達式看待,很其他語言一樣正常寫,該有分號的加分號。

不過,如果是 Go 程序員寫 Rust,很可能忘記分號。而 Rust 中,有時候有分號和沒有分號都能編譯,但意思可能變了,這個要特別注意。(PHPer 表示,經常在 PHP 和 Go 之間切換時,分號的問題很糾結,有木有?!

控制流程中的模式匹配,下節再講!

我是 polarisxu,北大碩士畢業,曾在 360 等知名互聯網公司工作,10 多年技術研發與架構經驗!2012 年接觸 Go 語言並創建了 Go 語言中文網!著有《Go 語言編程之旅》、開源圖書《Go 語言標準庫》等。

堅持輸出技術(包括 Go、Rust 等技術)、職場心得和創業感悟!歡迎關注「polarisxu」一起成長!也歡迎加我微信好友交流:gopherstudio

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/tO8CFBDZ2UsOonqARxOQUA