近日,微軟雲 Azure 的 CTO,Mark Russinovich 公開宣稱“是時候停止使用 C/C 啟動任何新項目”,并建議在需要使用 noc-GC 語言的場景下使用 Rust 編程語言。
要知道這位 Mark 老兄可是被稱為“地球上最有才華的 C/C 程序員”,他這番言論也是惹得 C 之父 Bjarne Stroustrup 親自下場對噴,稱其為“喜歡新事物的高管”,卻愛發表非常片面的言論。
Bjarne Stroustrup
這樁公案暫不多說,再看另一位業界大神——Linus Torvalds,宣稱要在 Linux 内核中加入 Rust 語言的代碼。要知道 Linus 大神當年試用了 C 兩周,就拒絕在 Linux 内核中使用 C ,并且把它噴了一輩子。
Linus Torvalds
能夠得到 Linus 大神的首肯,這當然是最好的背書。Linux 内核在發展三十年之後,終于在底層要迎來 C 之外的一門新語言。Rust 究竟有何魅力,讓大神們如此傾心?它真的會替代 C/C 語言嗎?
我們不妨從 Rust 的誕生之初說起。
Rust 的前世今生話說還是在 2006 年時,Mozilla 公司的員工 Graydon Hoare 搗騰了個私人項目——Rust,應該就是想弄個工具方便自己用。沒想到 Mozilla 公司看好此項目,于 2009 年開始贊助此項目。
Rust 也就從 Mozilla 走向了業界,2015 年發布了第一個穩定版本——Rust 1.0。這門系統編程語言,以其安全特性吸引了衆多程序員的目光,逐漸流行起來。更是連續七年蟬聯 StackOverflow 網站最受歡迎編程語言,可見其人氣爆棚。
不過令人唏噓的是,Mozilla 公司本打算用 Rust 重寫 Firefox,但因為業績不佳,不得不在 2020 年就将 Rust 核心開發團隊大部分成員給裁了。
好在熱心粉絲們又挺起了這個網紅項目,2021 年時正式成立 Rust 基金會,Mozilla 也将商标和基礎設施資産轉移給 Rust 基金會。走上獨立發展之路的 Rust,更是得到了谷歌等大佬的加持,在系統編程領域得以繼續攻城掠地。
Rust 是一種什麼類型的語言?很難給它貼一個标簽,例如像 C 是過程式的,像 C 是面向對象式的。作為一門現代語言,它支持泛型、函數式編程、面向對象等特性。運行效率可媲美 C/C ,看起來是相當的實用。
Rust 的最大賣點,就是其具備 C/C 所沒有的底層安全特性。那實際情況到底如何?讓我們先從最關心的安全特性一窺其究竟。
Rust 真的安全嗎?
Rust 語言的安全性,其實是相對于 C/C 來說的。首先要強調一點,就是 C/C 并非“不安全”的語言,隻是因為 C/C 的設計哲學,就是将控制權盡可能地交到程序員手中。
C/C 所具有的靈活性,讓它們得以在做系統開發時能夠控制幾乎一切。但所有的安全性工作,都要由程序員承擔。這就像是一件極其鋒利的武器,但你卻得光着膀子舞動它,會不會傷到自己,純看個人水平了。
可問題就在于,團隊中不可能個個都是大神,稍微不注意,就會出現懸垂指針、資源未釋放、越界訪問等問題。而這些隐患在 C/C 裡是可以編譯通過的,要等到運行起來才會出現最讓人頭痛的“Segmentation fault”錯誤。
Rust 的安全特性,就是解決了這些内存安全的問題,讓程序員可以專心于功能實現。這就好像給程序員穿上了一件黃金鎖子甲,武器鋒利不減,舞動起來卻再不擔心會傷到自己了。
具體來說,當你寫的 Rust 代碼裡存在安全隐患時,有的問題會在編譯時就報錯退出,有的則是在運行時産生一個panic崩潰提示。
我們可以看一個越界訪問的代碼示例:
fn main() {
let animals = vec!["tiger", "panda", "mouse"]; //隻定義了三個元素
let one = animals[4]; // 下标訪問第五個元素
println!("animal: {:?}", one);
}
這段代碼的運行結果是産生了一個panic崩潰,并給出了明确的提示:
若是在 C/C 中,越界訪問可能并不一定會讓程序崩潰,但崩潰時又會破壞堆棧信息,很難發現并調試。Rust 如此處理,顯然讓程序員可以更容易找到問題,并對這門語言充滿信心。
Rust 内存安全探秘Rust 有兩大賣點,一是實現了不依賴于GC(Garbage Collection,垃圾回收)的内存自動管理;二是可以放心編寫并發程序,号稱“無畏并發”(fearless concurrency)。
就這兩點,可以說是直擊 C/C 程序員的痛點。想想看,既不用操心手工分配、釋放内存,又不擔心性能損失,還能安心玩高并發,多美。
做到這兩件事,Rust 是基于三個語言特性:所有權、生命周期、借用。我們下面逐一進行解釋。
所有權:在 Rust 中,每個值都是具有所有權的,變量通過移動語義獲得值的所有權,從而成為所有者,且值隻能有一個所有者。例如賦值語句,就是所有權的“移動”,而非數據的複制,這是迥異于 C 語言之處。
生命周期:值的有效可訪問時間段,超出該時間段則資源會被釋放。生命周期與作用域的概念并不同相同,它與所有權緊密結合,從而實現非 GC 方式的自動内存回收。
借用:既然值有嚴格的所有權與生命周期,那麼程序中總會有共享訪問值的要求,在不移動所有權的情況下,就可以使用“借用”的方式。它通過引用來實現,支持隻讀引用與讀寫引用。
最核心的這三個概念,從文字上看可能并不好理解。畢竟程序員的格言是 talk is cheap, show me the code。我們就從一個實例出發,學習這三條原則。
Rust 開發上手
搭建 Rust 開發環境,我們以 Linux 環境為例,最簡單的方式就是執行一句腳本:curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh,成功執行完畢,Rust 的相關編譯工具也就安裝好了。
我們先編輯如下這段代碼,它定義了一個簡單的結構體Student,還定義了一個函數show_info(),它接受一個Student類型的參數,并返回一個u64的值。在主函數中對Student類型的變量one,用show_info()函數調用了兩次。
#[derive(Debug)]
struct Student{
id: u64,
}
fn show_info(stud: Student) -> u64 { //函數的返回值是無符号長整型
println!("student id: {:?}", stud.id);
stud.id //最後一個變量不加分号,表示返回這個值
}
fn main() {
let one = Student {id: 0}; //初始化變量 one
let id = show_info(one);
println!("return: {:?}", id);
let id2 = show_info(one);
println!("return again: {:?}", id2);
}
保存源碼至main.rs,然後執行rustc ./main.rs,結果卻出現編譯失敗提示:
如果你是剛接觸 Rust,還按着 C/C 的套路來寫這段代碼,估計會有想掀桌子的沖動。憑什麼這都編譯不過?莫急,讓我們根據上一節講的三條原則來剖析一下發生了什麼。
首先,标記代碼第 13 行的藍色字體,提示Student類型缺少trait Copy。struct類型默認沒有實現Copy方法,所以實際發生的是所有權移動,而非數據複制。
接着,第 14 行藍色字體的提示,就說明變量one在傳遞進show_info函數時,所有權發生了移動,one不再擁有值的所有權。标記第 17 行的紅色字體表明,變量one已經失效,而值的生命周期是在show_info函數調用時結束,并不是在主函數中結束。
這對于 C/C 程序員來說,可能比較反直覺的,就是賦值語句與函數參數傳遞,除非特别聲明,否則都是移動所有權。那麼如何調整代碼實現功能呢?這就可以通過“借用”技術來實現:
#[derive(Debug)]
struct Student{
id: u64,
}
fn show_info(stud: & Student) -> u64 { //參數傳入的是值的引用,而非移動所有權
println!("student id: {:?}", stud.id);
stud.id
}
fn main() {
let one = Student {id: 0};
let id = show_info(&one); //傳入值的引用
println!("return: {:?}", id);
let id2 = show_info(&one); //傳入值的引用
println!("return again: {:?}", id2);
}
代碼修改了三處,show_info()的參數修改為& Student,調用時傳參修改為&one。這樣編譯通過,&符号表示引用,可以理解為類似于 C 語言中的引用類型。
引用值不發生所有權轉移,也不改變值的生命周期。仔細體會一下,這種嚴格的規則,的确讓内存自動管理變得簡單了。示例代碼中實現是隻讀引用,讀寫引用要使用關鍵字mut,如何在show_info()裡修改Student::id的值,就留給各位去思考與實踐吧。
還有更多辦法解決所有權問題嗎?
《Rust 實戰》這本書裡提供了四條原則:
- 在不需要完整所有權的地方,使用引用;
- 重構代碼,減少長存活期的值;
- 在需要完整所有權的地方,複制長存活期的值;
- 把數據包裝到能幫助解決移動問題的類型中。
至于說到 Rust 是不是能完全替代 C/C 語言,并讓後者退出曆史舞台?其實我倒并不這麼認為,在相當長的一段時期内,C/C 仍然會擁有許多開發者,并且在語言特性上也會不斷發展。
Rust 最大的意義,就是給系統編程提供了一種新的可能。它也獲得了業界衆多大神的支持,一定會有更多像 “Rust for Linux” 這樣成功的項目不斷湧現。
對于程序員來說,空口争論哪門語言才是最好的,這毫無意義。重要的是去學習、嘗試使用新的編程語言,改善自己的工作績效,提高生産力,這才是正道。
讓我們重溫一下 Rust 的官方口号:一門賦予每個人構建可靠且高效軟件能力的語言。
,