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 上,我找到了一场关于使用编译器标志生成去语法糖输出的讨论。(注意,我在这里使用 rustup,通过 +nightly 标志调用 nightly 编译器。)

rustc +nightly -Zunpretty=hir example.rs

HIR 代表 high-level intermediate representation(高级中间表示)。它基本上是一种更适合编译器使用的 abstract syntax tree(抽象语法树,AST)。它会用更基本的构件替换语法糖,从而更便于后续编译步骤处理。想进一步了解,请阅读 Nico Matsakis 撰写的详细说明

Rustc 编译流程图。HIR 是第一步。

无论如何,输出结果出乎意料地易读(见下文)。加上一些语法高亮和格式化后,它完全可以成为一个相当实用的工具。

我尝试对它使用 rustfmt,结果好得有些出人意料。受到这个快速成果的鼓舞,我把它封装成一个 cargo 子命令,并将其命名为 cargo-inspect

让我们用一些真实代码试试 cargo-inspect!

示例——对范围表达式进行 desugaring(去语法糖)

以下示例也可以在项目的 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 上为其他人参与贡献开了几个 issue。具体来说,我希望能够有以下选项:

此外,如果你发现了特别有趣的代码示例,也不要拘谨,欢迎将其贡献到 examples 文件夹中。

原文由 Matthias Endler 发布

本文章由 openai/gpt-5.6-luna 进行翻译