Okay, so I lied a bit. The compiler doesn't directly call the expand
function. It calls indoc_impl
.
But not the indoc_impl
you see there. It calls another one. One that's auto-generated.
...yeah.
...um, you might wanna sit tight. It's a bit of a long story.
tl;dr: proc-macro-hack
is beautiful and terrible.
How are procedural macros made discoverable to rustc?
A procedural macro crate looks like this:
With those two things taken care of, rustc will discover the procedural macros, and somebody who uses your crate can will be able to write my_fun_macro!{ }
and #[derive(MyCoolTrait)]
respectively to use them.
Ahhh, so it's the #[proc_macro]
on line 35 that's important?
#[cfg(feature = "unstable")]
#[proc_macro]
pub fn indoc(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
expand(&input)
}
Nope! That one is annotated with a #[cfg]
block that normally prevents it from being compiled.
In fact, the important thing here is the #[proc_macro_derive(indoc_impl)]
that you don't see on line 42.
The... uh... wait, what?
You don't see it because it's generated by proc_macro_expr_impl
.
...uhhhhhhhhhh why?
Okay. Let's take a step back.
You can't actually use #[proc_macro]
on stable rust yet. Back when proc macros were first introduced (in an update referred to as "macros 1.1"), only #[proc_macro_derive]
was stabilized. (and with good reason; even to this day, #[proc_macro]
macros are not yet production-ready).
This means that currently it is impossible to write procedural macros that use macro_name!()
syntax.
B-b-b-but indoc
uses that syntax!!! So clearly it is possible!
Yes. Anything is possible when you work hard enough.
Many people were dying to use procedural macros for items and expressions no matter the cost. And so this is why dtolnay created proc-macro-hack
.
proc-macro-hack
?
proc-macro-hack
does beautiful and terrible things to make procedural macros work on stable rust.
Here's what really happens when you call indoc!
. I can't make this stuff up.
You write:
indoc!{"
howdy, world!
"}
it expands to:
{
#[derive(indoc_impl)]
#[allow(unused)]
enum ProcMacroHack {
Input = (stringify!("
howdy, world!
"), 0).1
}
proc_macro_call!()
}
which, I'd like to add, is a completely valid enum.
This causes rustc to directly call the procedural macro annotated with #[proc_macro_derive(indoc_impl)]
, which was generated here by proc_macro_expr_item
(which is part of the hack). The autogenerated code parses away all the useless stuff around the input tokens to find
"
howdy, world!
"
at which point the function here in indoc
is finally called. This function outputs
"howdy, world!\n"
which the autogenerated function slips into a new macro for reasons I guess:
{
macro_rules! proc_macro_call {
() => {
"howdy, world!\n"
}
}
proc_macro_call!()
}
which finally reduces to
{
"howdy, world!\n"
}
So... yep.