Rust 在幕後做了什麼?
Rust 提供了大量的 syntactic sugar(語法糖),讓撰寫程式變得十分愉快。然而,有時卻很難看透幕後,瞭解編譯器究竟對我們的程式碼做了什麼。
在 Rust Belt Rust 2018 上,我聽了一場由 Tshepang Lekhonkhobe(切潘·萊科恩科貝)發表的演講,題目為 編譯器提供的語法便利(在此觀看錄影)。
引述其摘要如下:
Rust 編譯器提供了許多便利功能,讓使用者開發更輕鬆。了解這些功能的原理,有助於避免對底層運作感到困惑……我們對世界的魔法式想像越少越好。
他接著舉了幾個這類便利功能的例子:
- lifetime elisions(生命週期省略)
- type inference(型別推論)
- syntactic sugar
- implicit dereferencing(隱式解參照)
- type coercions(型別強制轉型)
- hidden code(隱藏程式碼)(例如 prelude(前置匯入))
在演講中,看他對比使用與未使用這些便利功能的程式碼,既有啟發性又十分有趣。
回到家後,我想進一步了解。我在想是否有工具能揭示 Rust 在幕後做了什麼。
在 Reddit 上,我找到了一則關於使用編譯器旗標產生 desugared output(去語法糖後輸出) 的討論。(請注意,我在此使用 rustup 並加上 +nightly 旗標來啟用 nightly 編譯器。)
rustc +nightly -Zunpretty=hir example.rsHIR 是 high-level intermediate representation(高階中間表示) 的縮寫。這基本上是一種更適合編譯器使用的 abstract syntax tree (AST)(抽象語法樹)。它會將 syntactic sugar 以更基礎的構成元件取代,讓後續的編譯步驟更容易處理。若想深入了解,請閱讀 Nico Matsakis(尼可·馬薩基斯)撰寫的這篇詳細說明。
無論如何,輸出結果出乎意料地易於閱讀(見下文)。加上語法突顯與格式化後,這可能會是個相當實用的工具。
我試著用 rustfmt 來處理它,效果好得出乎意料。受到這個小小成功的鼓舞,我將其包裝成一個 Cargo 子命令,並命名為 cargo-inspect。
讓我們在實際的程式碼上試試 cargo-inspect 吧!
範例 — 範圍表達式的去語法糖
下列範例也可以在專案的 examples 資料夾中找到。
輸入:
for n in 1..3 {
println!("{}", n);
}cargo-inspect 的輸出:
這是經過整齊格式化的終端機輸出。它具備行號與色彩,這要歸功於 prettyprint,一個建構於 bat 之上的函式庫。或許你無法看清楚圖片,所以這裡是其精華:
match ::std::iter::IntoIterator::into_iter(
::std::ops::Range { start: 1, end: 3 })
mut iter => loop {
// ...
},
};我們可以看到 1..3 被轉換為 std::ops::Range { start: 1, end: 3 }。對編譯器後端而言,兩者完全相同。因此以下斷言成立:
assert_eq!((1..3), std::ops::Range { start: 1, end: 3 });範例 — 檔案處理
輸入:
use std::fs::File;
use std::io::Error;
fn main() -> Result<(), Error> {
let file = File::open("file.txt")?;
Ok(())
}輸出:
#[prelude_import]
use std::prelude::v1::*;
#[macro_use]
extern crate std;
use std::fs::File;
use std::io::Error;
fn main() -> Result<(), Error> {
let file = match ::std::ops::Try::into_result(
<File>::open("file.txt")) {
::std::result::Result::Err(err) =>
#[allow(unreachable_code)]
{
#[allow(unreachable_code)]
return ::std::ops::Try::from_error(
::std::convert::From::from(err))
}
::std::result::Result::Ok(val) =>
#[allow(unreachable_code)]
{
#[allow(unreachable_code)]
val
}
};
Ok(())
}我們可以看到傳遞運算子 ? 被去語法糖展開為針對 File::open 的 Result 進行的 match。若發生錯誤,我們會套用 std::convert::From::from 來轉換錯誤型別。否則,就只是回傳 Ok 的值。
演講
在比利時的 FOSDEM 大會上,我得以詳細地介紹這個專案。以下是演講錄影:
未來工作
我並不打算在此重寫編譯器。rustc 做得比我能做到的好太多了。所有這些功能本來就已經存在;我只是試著讓編譯器對像我這樣的學習者更親切易懂。
目前,這個工具還相當脆弱。當出現問題時,它會拋出難看的錯誤訊息。它主要在針對小型、獨立的範例片段執行時最能發揮作用。
一起參與!
在 Github 上,我開了幾個議題,歡迎大家參與。具體來說,我希望能有以下選項:
另外,如果你發現特別有趣的程式碼範例,也歡迎毫不猶豫地將它貢獻到 examples 資料夾。
隨機一篇部落格
