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.rsHIR 代表 high-level intermediate representation(高级中间表示)。它基本上是一种更适合编译器使用的 abstract syntax tree(抽象语法树,AST)。它会用更基本的构件替换语法糖,从而更便于后续编译步骤处理。想进一步了解,请阅读 Nico Matsakis 撰写的详细说明。
无论如何,输出结果出乎意料地易读(见下文)。加上一些语法高亮和格式化后,它完全可以成为一个相当实用的工具。
我尝试对它使用 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 文件夹中。
随机一篇博客
