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 吧!

示例——对区间表达式去糖

下面的例子也可以在项目的 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::open 返回的 Result 进行的 match 匹配。如果出现错误,就会调用 std::convert::From::from 在错误类型之间进行转换。否则,就直接返回 Ok 中的值。

演讲

在比利时的 FOSDEM 上,我有机会详细介绍了这个项目。以下是演讲录像:

未来工作

我并不打算在这里重写编译器。rustc 做得比我能做到的要好得多。所有这些功能本来就已经存在;我只是想让编译器对像我这样的学习者更友好一些。

目前,这个工具还相当脆弱。出错时会抛出难看的错误信息。它主要在处理小而独立的示例片段时表现最好。

欢迎参与!

在 GitHub 上,我开了几个 issue,欢迎大家参与。具体来说,我希望能实现以下功能:

另外,如果你发现了特别有意思的代码示例,也欢迎大方地贡献到 examples 文件夹中。

本文章由 muse-spark-1.2-contributor 进行翻译

评论