What Is Rust Doing Behind the Curtains?
— Find out with cargo-inspect!
Rust allows for a lot of syntactic sugar, that makes it a pleasure to write. It is sometimes hard, however, to look behind the curtain and see what the compiler is really doing with our code.
At Rust Belt Rust 2018, I saw a talk by Tshepang Lekhonkhobe titled Syntax conveniences afforded by the compiler (Recording here).
To quote the abstract:
The Rust compiler provides a number of conveniences that make life easier for its users. It is good to know what these are, to avoid being mystified by what’s going on under the hood… the less magical thinking we have of the world, the better.
He goes on to give a few examples of these conveniences:
- lifetime elisions
- type inference
- syntactic sugar
- implicit dereferencing
- type coercions
- hidden code (e.g. the prelude)
It was very educational and fun to see him compare code with and without these conveniences during the talk.
Coming home, I wanted to learn more. I wondered if there was a tool, which revealed what Rust was doing behind the curtains.
Over on Reddit, I found a discussion about compiler flags to produce desugared output. (Note that I’m using rustup here to trigger the nightly compiler with the +nightly
flag.)
rustc +nightly -Zunpretty=hir example.rs
HIR
stands for high-level intermediate representation. This is basically an abstract syntax tree (AST) more suited for use by the compiler. It replaces syntactic sugar with basic building blocks that are easier to handle by the following compile steps. To find out more, read this detailed write-up by Nico Matsakis.
Anyway, the output looked surprisingly readable (see below). With some syntax highlighting and formatting, this could be quite a handy tool.
I tried to use rustfmt
on it, and it worked unreasonably well. Motivated by this quick win, I wrapped it up in a cargo subcommand and called it cargo-inspect
.
Let’s try cargo-inspect on some real code!
Example - Desugaring a range expression
The following examples can also be found in the project’s examples
folder.
Input:
for n in 1..3
Output of cargo-inspect:
That’s the neatly formatted terminal output. It sports line numbers and colors, thanks to prettyprint
, which is a library on top of bat
. Maybe you can’t read that, so here’s the gist of it:
match into_iter
mut iter => loop ,
};
We can see that 1..3
gets converted into std::ops::Range { start: 1, end: 3 }
. To the compiler backend, these are absolutely the same. So this holds:
assert_eq!;
Example - File handling
Input:
use File;
use Error;
Output:
use *;
extern crate std;
use File;
use Error;
We can see that the carrier operator ?
gets desugared into a match
on the Result
of File::open
. In case of an error, We apply std::convert::From::from
to convert between error types. Otherwise, we simply return the Ok
value.
Talk
Over at FOSDEM in Belgium, I was able to speak about the project in detail. Here is the recording:
Future work
I’m not planning to rewrite the compiler here. rustc
is doing a far greater job than I could. All this functionality already existed before; I’m merely trying to make the compiler more approachable for learners like me.
Right now, the tool is quite fragile. It throws ugly error messages when things go wrong. It mostly shines, when you run it on small, isolated example snippets.
Get involved!
Over on Github, I opened up a few issues for others to get involved. Namely, I wish there were options to:
- Make it work with cargo projects.
- Show the original code above the desugared code.
- Show only part of the full output
- …and much more.
Also, if you find a particularly exciting code example, don’t be shy to contribute it to the examples folder.
Thanks for reading! I mostly write about Rust and my (open-source) projects. If you would like to receive future posts automatically, you can subscribe via RSS.