Rust 這個語言算是久仰大名了。我在好幾年前嘗試研究怎麼樣整合 HTML layout engine時,就有接觸到 Servo 項目。相較於其他 layout engine,Servo 的開發語言是比較特殊,也就是現在大家所知道的 Rust。
Rust 主張兼具效能與安全性,這兩個主題通常是背離的。癥結點往往是在 pointer 上,pointer 賦予程式設計師自由,卻也提高了對程式設計師素養的要求。即使是在 smart pointer 被廣泛運用的現代軟體開發中,只要遇到異步處理,資源的有效性都不會是一個簡單的議題(特別是那些對效能有極高要求的應用情境)。
解決這些問題,Rust 語言導入了 Ownership 的觀念,我們來看看官方例子
fn main() {
let s1 = String::from("hello");
let s2 = s1;
println!("{}, world!", s1);
}
這段程式碼,是無法通過編譯的。並不是因為其語法有錯,而是它違反了 Rust 對物件所有權的規範。當 s1 被指派到 s2 時,s1 已不參考任何物件。
為何是指派不是賦值? assign 在台灣經常會被翻譯成賦值,特別是在 C 語言教科書。對於 C、C++ 來說,以值來解釋 assignment 並沒有錯。然而對於那些參考型別(Reference Type),
=的意義就必須由上下文判斷。
認識 C++ 的工程師,肯定能馬上聯想到:這不就是 unique_ptr 嗎?如果你將這段程式碼改為:
fn main() {
let mut s1 = String::from("hello");
let s2 = s1;
s1 = String::from("world");
println!("{}, {}!", s2, s1);
}
那就完全沒問題。Rust 編譯器可說是在靜態分析上下足了苦功,這也就導致了一個嚴重問題,絕對大多數開始學習 Rust 的人都把時間花在如何讓程式編譯過上:對新手而言,並不是一條平易的道路。
我們來看一下這個片段:
struct A {
text: str
}
fn main() {
let a = A {
text: String::from("Hello World")[..]
};
println!("{}!", a.text);
}
這段程式碼同樣也無法通過編譯:
error[E0277]: the size for values of type `str` cannot be known at compilation time
但寫成:
struct A {
text: String
}
fn main() {
let a = A {
text: "Hello World".to_string()
};
println!("{}!", a.text);
}
就完全沒問題,為什麼? 對初學者來說,這恐怕有些費解,但很遺憾地是 str 並不是 String 型別的別稱,str 指的是 String 的 slice。因此 slice 作為 field 的大小自然是無法確認,當然無法通過編譯。對那些母語不是 C 或 C++ 的工程師而言「為什麼型別大小如此重要」在學習 Rust 就是必須了解的一個課題。
有人認為 Rust 是集合近幾十年程式語言範式跟思維的語言,我認為這話確實有他的道理,也意味著使用者必須要了解這些語言特性背後的思維與涵義終究有著什麼意涵,而不是囫圇吞棗、死記硬背。
趁著這次勞動節連假,我終於靜下心來決定好好認識 Rust。越是學習就越是能體會到 Rust 思維的樂趣,以我的觀點來看 Rust 從部屬到測試甚至還有學習研究,都要比 C++ 簡單不少(如果拿C++與Rust的STL原始碼作比較,我相信你會跟我得出一樣的結論)。Rust 的 Algebraic data type(代數資料型別) 與方便的 Macro,能夠讓 Metaprogramming 變得更加輕鬆寫意。