Learn
Proc macros
Code that writes code
A proc macro is a small Rust program that runs while your code compiles.
#[derive(Serialize)] is one: it reads your struct and writes the code that lets serde turn it into JSON or another format.
It runs every time its crate compiles
The code a macro writes is code your crate needs, so the compiler has to run the macro before it can compile what's around it. Every use of a macro is one more run.
Why not keep the answer?
If the same macro gets the same input, you'd want to keep its output and skip the run.
But a normal proc macro is native code: it can read any file, check the clock or use the network, so next time it might answer differently. Keeping what it wrote is a bet that it didn't.
A macro that reads the time
A macro reads the current time and writes it into your code. Why does that stop anyone keeping its output?
- Reading the time is slow.Speed isn't the problem. The problem is that a kept copy would hold an old time.
- Run again, it would write something different, so a kept copy could be wrong.The output depends on something that isn't its input, so the same input no longer means the same output.
- Proc macros aren't allowed to read the time.A normal proc macro can do anything a program can, including read the clock. That's exactly the trouble.
Show the answer
Run again, it would write something different, so a kept copy could be wrong. The output depends on something that isn't its input, so the same input no longer means the same output.
A sealed box
blaze builds each proc macro to WebAssembly and runs it in a sandbox.
Inside, the macro sees only its own package, read-only, and the variables cargo gives it. Its clock reads zero, and its random numbers are the same on every run.
What can it read?
Tap everything a proc macro can read inside blaze's sandbox.
It sees its own package and what cargo hands it. Nothing else.
Show the answer
Its own package, read-only, and cargo's variables for the crate. Your home folder, the network and other crates' files are all outside the box.
Now the answer can be kept
Sealed like that, an expansion is a pure function: the same macro with the same input gives the same output, every time.
So blaze keeps each expansion in its store and, when the same input comes again, uses the kept copy without running the macro. For now only the same checkout finds them.
Why WebAssembly?
Why does blaze run proc macros as sandboxed WebAssembly?
- Because WebAssembly runs faster than native code.Usually it doesn't. The point is what the macro can't reach, not speed.
- So macros can be written in other languages.They're still Rust macros, built from the same source. The sandbox is about what they can reach.
- So the same input always gives the same output, which makes the output safe to keep.Sealed off from everything but its input, a macro becomes a pure function, and a pure function's answer can be kept.
Show the answer
So the same input always gives the same output, which makes the output safe to keep. Sealed off from everything but its input, a macro becomes a pure function, and a pure function's answer can be kept.
Build scripts too
build.rs scripts get the same idea, in an operating-system sandbox: the machine is read-only to them, the only place they really write is their own output folder, and there is no network. So their runs can be kept too.
Discussion
Questions, corrections, ideas: anything about this page.
Sign in with GitHub to comment.

