Start here: intro-rust-mir.
MIR: mid-level intermediate representation, it comes between the HIR and LLVM.
MIR enables much more flexible borrowing, more information here.
MIR reduces Rust down to a simple core, removing almost all the syntax such as for
loops, match
expressions and method calls. Instead, they are translated into primitives.
Example
for elem in vec {
process(elem);
}
This iterates over all elements in a vector and processes them one by one.
A for
loop in Rust, converts a value into an iterator and repeatedly calls next
on that iterator. A while let
loop iterates until some condition is met. We can rewrite the for
loop into a while let
loop:
let mut iterator = vec.into_iter();
while let Some(elem) = iterator.next() {
process(elem);
}
We still have multiple kinds of loops, now we can rewrite it into a simple loop
combined with a match
:
let mut iterator = vec.into_iter();
loop {
match iterator.next() {
Some(elem) => process(elem),
None => break,
}
}
We already eliminate two constructs (for
and while
loops), but we can go further. More information here:
// Rather than `vec.into_iter()`, we are calling
// the function `IntoIterator::into_iter`. This is
// exactly equivalent, just more explicit.
let mut iterator = IntoIterator::into_iter(vec);
loop {
// Similarly, `iterator.next()` can be rewritten
// to make clear which trait the `next` method
// comes from. We see here that the `.` notation
// was also adding an implicit mutable reference,
// which is now made explicit.
match Iterator::next(&mut iterator) {
Some(elem) => process(elem),
None => break,
}
}
Now to reduce even more we can use goto
:
let mut iterator = IntoIterator::into_iter(vec);
loop:
match Iterator::next(&mut iterator) {
Some(elem) => { process(elem); goto loop; }
None => { goto break; }
}
break:
...
MIR construction is type-driven. We saw that all method calls like
iterator.next()
can be desugared into fully qualified function calls likeIterator::next(&mut iterator)
. However, doing this rewrite is only possible with full type information, since we must (for example) know the type ofiterator
to determine which trait thenext
method comes from. In general, constructing MIR is only possible after type-checking is done.
MIR makes all types explicit. Since we are constructing MIR after the main type-checking is done, MIR can include full type information. This is useful for analyses like the borrow checker, which require the types of local variables and so forth to operate, but also means we can run the type-checker periodically as a kind of sanity check to ensure that the MIR is well-formed.