What Is Rust Doing Behind the Curtains?

Matthias Endler

Rust 在幕後做了什麼?

原文由 Matthias Endler 發布,訂閱此部落格

Rust 提供了大量的語法糖,讓撰寫程式變得十分愉快。然而,有時卻很難看透幕後,弄清楚編譯器究竟對我們的程式碼做了什麼。

Rust Belt Rust 2018上,我看了一場由 Tshepang Lekhonkhobe 發表的演講,題目是Syntax conveniences afforded by the compiler錄影在此)。

引述其摘要如下:

Rust 編譯器提供了許多便利功能,讓使用者的生活更輕鬆。了解這些是什麼很有幫助,才能避免對底層運作感到困惑……我們對世界的魔法想像越少,就越好。

接著他舉了幾個這類便利功能的例子:

  • 生命週期省略
  • 型別推斷
  • 語法糖
  • 隱式解參考
  • 型別強制轉換
  • 隱藏的程式碼(例如prelude

在演講中,看他對比使用與未使用這些便利功能時的程式碼,既有啟發性又很有趣。

回家後,我想進一步了解。我在想,是否有什麼工具能揭示 Rust 在幕後做了什麼。

我在 Reddit 上找到了一則關於用編譯器旗標產生解糖後輸出的討論。(請注意,我在這裡使用rustup並加上+nightly旗標來啟用 nightly 版編譯器。)

rustc +nightly -Zunpretty=hir example.rs

HIR是 high-level intermediate representation(高階中間表示法)的縮寫。它基本上是一個抽象語法樹(AST),更適合編譯器使用。它會將語法糖替換為更基礎的構成單元,讓後續的編譯步驟更容易處理。若想進一步了解,請閱讀這篇由 Nico Matsakis 撰寫的詳細說明

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

總之,輸出結果出乎意料地易讀(如以下所示)。只要加上一些語法突顯和排版,這就會是個相當實用的工具。

我試著用rustfmt來處理它,效果好得不可思議。受到這個小小成功的鼓舞,我將它包裝成一個 cargo 子命令,並命名為cargo-inspect

讓我們來用 cargo-inspect 試試一些實際的程式碼吧!

範例 - 解糖 range 運算式

以下範例也可以在專案的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 上開了幾個 issue,讓有興趣的人可以參與。具體來說,我希望能有以下選項:

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

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

留言