https://zhuanlan.zhihu.com/p/90612241html
今天(2019-11-07)Rust終於發佈了期待已久的v1.39版本,增長了重量級的async
/await
關鍵字支持。Rust做爲一個2015年才發佈正式版的新星,使用人數寥寥,卻能在StackOverflow發起的「最喜好的編程語言」年度投票中連續四年蟬聯第一。Rust憑什麼可以擊敗Python等衆多語言連續四年制霸?這一切的背後有着什麼樣的祕密?是人性的扭曲仍是道德的淪喪?編程
衝着async
/await
支持,筆者最近在一個小項目中試用了Rust v1.39。雖然只是個小工程,寫完後深入體會都了Rust無與倫比的優點。瀏覽器
幾十年以來,C/C++一直都是操做系統內核、瀏覽器、NGINX、Redis等這類性能關鍵(Performance Critical)軟件開發的首選語言。但存在的問題也很是明顯:空指針、野指針、內存越界等。輕則Segment Fault崩潰,重則致使難以發現的0-day漏洞。安全
寄但願於開發人員不犯一丁點錯誤寫出內存安全的軟件無異於癡人說夢。所以自90年代開始,以Java爲表明的一系列自動化內存管理的編程語言興起。經過在性能方面作出適當的妥協和讓步,引入GC等機制,來實現內存安全。網絡
今後,軟件開發經常面臨這樣的兩難選擇:要麼爲了安全性而在性能上作出妥協;要麼爲了極致的性能犧牲安全性。例如在使用Java開發某網絡服務時,爲了實現zero-copy轉發,JVM管理的byte[]
已經沒法知足要求,須要使用不受GC管理的Direct Buffer。好比採用netty.io中引用計數管理的ByteBuf
,使用時慎之又慎,不然很是容易致使內存泄漏或者內存提早釋放,特別是加上邏輯分支異常處理的狀況下這些錯誤不免出現。從那時起,在性能與安全性的選擇上彷佛就變成了「魚與熊掌不可兼得」的難題。數據結構
而Rust在設計之初就設定了必須同時兼顧性能和安全性的目標——既要有"底層語言"同樣的性能,又要有"高層語言"的安全性(甚至在線程安全性方面比Java/C#等語言更加安全!)。這樣一種決不妥協的設計哲學,讓Rust從一出生開始就註定了它的不平凡。多線程
Rust兼顧「低層」和「高層」語言的特性Rust沒有傳統的垃圾回收器。爲了解決內存安全性,Rust首創了全部權(Onwership)系統與租借檢查器(Borrow Checker)。閉包
好比下面的代碼併發
s
是一個referent,它指向內存中的一個字符串對象。編譯器編譯代碼時候,會在referent的做用域範圍結束位置自動生成代碼銷燬所指的內存對象。即referent擁有着此內存對象的全部權。全部權也可以從一個referent轉移到另外一個referent,亦即全部權轉移(move)。不管怎麼轉移,對於內存中的某個對象在同一時間內有且僅有一個referent得到了它的全部權。聽上去referent有點像C++中的auto_ptr
智能指針,但Rust的referent更加細膩和強大。框架
b1
/b2
/b3
看上去很像指針,在Rust中它們被稱爲租借(borrow)。
b1
是可變(mutable)租借,而b2
/b3
屬於不可變(immutable)租借。關於referent和borrow的詳細規則可見下圖(原圖地址)。
全部的這些規則都由編譯器實現。簡而言之,Rust經過編譯期的規則實現了沒有GC下的內存安全。
在內存安全的基礎上,Rust更進一步地保證了線程安全性。
Send trait
的數據類型全部權可以在多個線程間轉移,但線程間不存在共享數據。Sync trait
的數據類型可以被多個線程同時訪問,表明着該數據類型實現了線程間的同步操做。須要說明的是,Send
和Sync
都屬於編譯器自動添加的標記(marker trait),並不須要開發人員實現。若是某個數據類型全部成員都是Send
或Sync
的,那麼該數據類型也會被自動標記爲Send
或Sync
。這樣若是代碼可以編譯經過就天然而然地說明線程安全。
benchmark顯示Rust有着媲美C/C++的效率,就執行速度而言狂虐Golang/Java。
在TechEmpower進行的主流Web框架性能測試中,由微軟員工Kim使用Rust構建的actix開源框架在多項測試場景中拔得頭籌。
依據歷年的數據,70%的安全漏洞都和內存安全有關。Rust完美解決了安全性和性能的兩難問題,也讓它成爲了開發底層應用的更優選擇。目前,Intel、微軟和Linux都在積極準備使用Rust開發驅動程序甚至操做系統內核以改善日益嚴峻的安全問題。
近年來CVE中內存安全類漏洞比例圖Rust在設計之初被定位爲多範式語言(multi-paradigm language),在汲取了其它語言大量成功經驗基礎上,並不限定用戶採用某種特定的範式。
Rust支持傳統的命令式風格(imperative style)代碼無須贅述。Rust同時也吸收了大量ML陣營函數式風格(functional style)編程語言的諸多特性,好比模式匹配、不可變量與無反作用方法、Haskell類型系統、反向類型推導和trait特性系統等。這使Rust擁有極致的性能和對底層細膩的掌控能力的同時,具有高度抽象的表達能力。
使用Rust進行多線程應用開發,一樣擁有極高的自由度。
對於高併發I/O應用的開發,異步編程是首選項。傳統的異步依賴於回調或者閉包,實現起來麻煩不說,代碼可讀性也差強人意。而Rust此次新添加的兩個關鍵字相似於C#和Python中的async
/ await
,用戶無需再依賴於回調或者閉包,用最天然的方式編寫可讀性佳的I/O操做代碼,代碼看上去和同步代碼無異,而編譯器將異步調用部分編譯爲狀態機從而實現異步執行。這點感受很是好。
Rust學習曲線略爲陡峭,突破新手期所需時間數倍於Golang/Python,若是有着C++和Haskell經驗對這一階段幫助很大。突破新手期後就一片坦途,再也不像編寫C/C++程序那樣謹小慎微,而async
/await
關鍵字的引入大大方便了異步編程,不少時候感受都在用C#/Java這樣的高層語言編程同樣--關注於業務和程序結構而不用去擔憂某個對象錯誤地釋放。
須要吐槽的是各自爲戰的生態系統,爲了熟練使用async
/await
關鍵字,關於future就須要知曉std::future::Future
和futures::future::Future
, 然後者還分爲0.1版本和0.3版本。又因爲不一樣的庫依賴於不一樣的future實現,因此還須要精通它們之間各類轉換,開始時真有點頭大╮(﹀_﹀)╭。
瑕不掩瑜。Rust能夠說是目前最全面的編程語言。它既能夠用於開發與硬件打交道的底層,也能用來開發對性能有極致要求的微服務,甚至還能夠開發運行在瀏覽器中的WebAssembly網站。在擁有極致性能的同時又不失魯棒性和安全性。編程風格自由且高效,可以被普遍的其它語言開發者(C++/Haskell/Python/Scala等)接納。這些優點決定了Rust將會大放異彩!
最後來一段使用Seed開發的WebAssembly瀏覽器頁面代碼展現:
fn view(model: &Model) -> impl View<Msg> {
let plural = if model.count == 1 {""} else {"s"};
// Attrs, Style, Events, and children may be defined separately.
let outer_style = style!{
St::Display => "flex";
St::FlexDirection => "column";
St::TextAlign => "center"
};
div![ outer_style,
h1![ "The Grand Total" ],
div![
style!{
// Example of conditional logic in a style.
St::Color => if model.count > 4 {"purple"} else {"gray"};
St::Border => "2px solid #004422";
St::Padding => unit!(20, px);
},
// We can use normal Rust code and comments in the view.
h3![ format!("{} {}{} so far", model.count, model.what_we_count, plural) ],
button![ simple_ev(Ev::Click, Msg::Increment), "+" ],
button![ simple_ev(Ev::Click, Msg::Decrement), "-" ],
// Optionally-displaying an element
if model.count >= 10 { h2![ style!{St::Padding => px(50)}, "Nice!" ] } else { empty![] }
],
success_level(model.count), // Incorporating a separate component
h3![ "What are we counting?" ],
input![ attrs!{At::Value => model.what_we_count}, input_ev(Ev::Input, Msg::ChangeWWC) ]
]
}
#[wasm_bindgen(start)]
pub fn render() {
seed::App::build(|_, _| Init::new(Model::default()), update, view)
.build_and_start();
}