NOTE: the compiler is in a transition from being pass-based (sequential) to being query-based (on-demand).
Assumes we are doing a normal build, which means taking Rust source code and transforming it into binary format (i.e. something a CPU will understand)
-
process compiler flags, of which there's plenty
-
lexing source code (converts Unicode characters into tokens)
-
parsing (turns tokens into an AST)
-
macro expansion and code elimination (i.e. cfg processing)
-
lower to HIR (includes sugar removal)
note: query-based compilation begins here
-
type inference and type checking
-
lower to MIR (a far more simple version of "rust" which is suitable for borrow checking)
-
borrow checking (what makes Rust you-neek)
Examples of what happens at this stage:
- bindings can't be used uninitialised
- values can't be accessed once moved
- values can't be moved while still borrowed
- values can't be accessed while still mutably borrowed
-
some optimization happens, to reduce the input to the next stage, which helps with build times
-
convert to LLVM IR
Generics are expanded here (monomorphization)
-
(optional) optimize
-
convert to machine code (binary)
-
(in case of executables) linking