This blog series (in short: TIL) is supposed to cover topics in software development, learnings from working in software companies, tooling, but also private matters (family, baby, hobbys).
Hacking on the Rust language — error messages
Right now I’m totally digging Rust, a modern systems programming language which covers for instance thread-safety and memory access checks at compile time to guarantee code safety — while still being close to hardware like C/C++ — and has many more benefits such as a well-designed standard library, fast paced community and release cycle, etc.
Since I’m professionally working in C++, I am currently drafting a presentation that compares C++ with Rust with the goal of finding out where C++ could improve. The plan is to first present the slides in the Munich C++ meetup when completed.
One topic where C++ lags behind are macros — in Rust (macro documentation), one can match language elements, instead of doing direct text preprocessing (pre = before the compiler even parses the code).
// Simple macro to autogenerate an enum-to-int function
macro_rules! enum_mapper {
// `ident` means identifier, `expr` is an expression. `,*` is comma-separated repetition (optional trailing comma)
( $enum_name:ident, $( ($enum_variant:ident, $int_value:expr) ),* ) => {
impl $enum_name {
fn to_int(&self) -> i32 {
match *self {
// I can put comments in a macro, and don't need to have backslashes everywhere!
$(
$enum_name::$enum_variant => $int_value
),* // repetition consumes matches in lockstep
}
}
}
};
}
// Totally stupid enum
#[derive(Debug)]
enum State {
Succeeded,
Failed(String), // error message
Timeout,
}
enum_mapper!(
State,
(Succeeded, 1),
(Failed, 2),
(Timeout, 3)
);
fn main() {
let st = State::Failed("myerror".to_string());
println!("{:?} maps to int {}", st, st.to_int());
}
That code snippet produces the error error[E0532]: expected unit struct/variant or constant, found tuple variant
with the nightly compiler. To me, reading such a verbose error was like learning C++ in my
childhood — I just had no idea of the terminology used with the language, so "unit struct/variant" and "tuple variant"
were totally unclear to me and not immediately intuitive. The displayed error location also wasn’t helpful, and neither
provided me the expanded macro, nor the failing code line. In this sense, the error messages are on par with the C++
preprocessor (just as bad 😂). Normally, Rust provides error explanations with examples, displayed by
State::Failed
rustc --explain E0532
. But in this case: error: no extended information for E0532
.
So I found out myself — removing the variant parameter (String
) from State::Failed(String)
(so the enum only has
simple variants), my macro was working fine, and after some thinking it was clear that I had previously commented
out the consideration of variant parameters (that’s how I call them at the moment). Here’s how I could match
State::Failed(String)
:
$enum_name::$enum_variant(..) => $int_value
Note that this is not a solution because now it won’t match State::Succeeded
and State::Timeout
anymore (maybe it
used to work earlier), but this article is more about getting to
understand the problem by the error message.
Having found the problem, I still didn’t feel happy because that debug session cost me time and might happen again for me and surely for others as well. Hence, let’s hack Rust!
Getting started with hacking Rust is elegantly simple: clone,
./configure
, make
. That will build the whole compiler and rustdoc toolchain, but not the cargo build tool, but
that’s already enough hard disk space and download/build time spent. On my slow connection, the configure
script was
still cloning LLVM after 15 minutes 🐢💨…
make tips
hints at different targets to limit what is built, like if you’re only working on the compiler
(→ make rustc-stage1
, make <target-triple>/stage1/bin/rustc
).
In the meantime I searched the code for existing error messages (just grepped for the one of E0003), and immediately
found the source file src/librustc_const_eval/diagnostics.rs
. I found it strange that the list of diagnostic error
messages was so short, so I did ag -l '^\s*E[0-9]{4}:'
and discovered that the error messages belong to the respective
crate. In case of the example error I grepped (E0003), it’s the crate for "constant evaluation on the HIR and code to
validate patterns/matches" (HIR stands for High-level Intermediate Representation). My desired error explanation
should therefore go into the right crate, which turned out to be librustc_resolve
.
Finally the compiler build completed, but to my surprise, x86_64-apple-darwin/stage1/bin/rustc --explain E0003
could
not find an explanation. That was peculiar as stage 1 already should give me a working compiler (as of the writeup in
make tips
and the great summary Contributing to the Rust compiler).
The solution of the riddle was easy: E0003 has vanished with the following commit:
commit 76fb7d90ecde3659021341779fea598a6daab013
Author: Ariel Ben-Yehuda <ariel.byd@gmail.com>
Date: Mon Oct 3 21:39:21 2016 +0300
remove StaticInliner and NaN checking
NaN checking was a lint for a deprecated feature. It can go away.
Using another error code, it displays the explanation just fine, e.g.
x86_64-apple-darwin/stage1/bin/rustc --explain E0004
. Only missing point was to get in my desired explanation and
example for E0532, and test it in the same way. This part is too detailed for a blog post, but I ultimately ended up
with a pull request (still pending at the time of writing).
Now I’m happy to have started my first contribution. There will surely be more blog posts following about my experiences with Rust!