What Is Rust Doing Behind the Curtains?

Matthias Endler

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.rs

HIR 是 high-level intermediate representation(高階中間表示) 的縮寫。這基本上是一種更適合編譯器使用的 abstract syntax tree (AST)(抽象語法樹)。它會將 syntactic sugar 以更基礎的構成元件取代,讓後續的編譯步驟更容易處理。若想深入了解,請閱讀 Nico Matsakis(尼可·馬薩基斯)撰寫的這篇詳細說明

Rustc 編譯流程圖。HIR 是第一個步驟。

無論如何,輸出結果出乎意料地易於閱讀(見下文)。加上語法突顯與格式化後,這可能會是個相當實用的工具。

我試著用 rustfmt 來處理它,效果好得出乎意料。受到這個小小成功的鼓舞,我將其包裝成一個 Cargo 子命令,並命名為 cargo-inspect

讓我們在實際的程式碼上試試 cargo-inspect 吧!

範例 — 範圍表達式的去語法糖

下列範例也可以在專案的 examples 資料夾中找到。

輸入:

for n in 1..3 {
    println!("{}", n);
}

cargo-inspect 的輸出:

Range 輸出

這是經過整齊格式化的終端機輸出。它具備行號與色彩,這要歸功於 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::openResult 進行的 match。若發生錯誤,我們會套用 std::convert::From::from 來轉換錯誤型別。否則,就只是回傳 Ok 的值。

演講

在比利時的 FOSDEM 大會上,我得以詳細地介紹這個專案。以下是演講錄影:

未來工作

我並不打算在此重寫編譯器。rustc 做得比我能做到的好太多了。所有這些功能本來就已經存在;我只是試著讓編譯器對像我這樣的學習者更親切易懂。

目前,這個工具還相當脆弱。當出現問題時,它會拋出難看的錯誤訊息。它主要在針對小型、獨立的範例片段執行時最能發揮作用。

一起參與!

在 Github 上,我開了幾個議題,歡迎大家參與。具體來說,我希望能有以下選項:

另外,如果你發現特別有趣的程式碼範例,也歡迎毫不猶豫地將它貢獻到 examples 資料夾。

原文由 Matthias Endler 發布

本文章由 muse-spark-1.2-contributor 進行翻譯