Rust
proc_macro crate
Get to know the proc_macro crate and its core API.
The proc_macro crate
Procedural macros are ordinary Rust functions that run at compile time: they take a TokenStream and return a new TokenStream. They must be defined in a separate crate with proc-macro = true:
# Cargo.toml (macro crate)
[lib]
proc-macro = true
// src/lib.rs
use proc_macro::TokenStream;
#[proc_macro_derive(Merhaba)]
pub fn merhaba_turet(girdi: TokenStream) -> TokenStream {
// input: the tokens of the struct definition
// output: the impl block to generate
todo!()
}
There are three kinds: #[derive(..)] macros, attribute macros (#[route(..)]) and function-like macros (sql!(..)).
What does it produce?
For #[derive(Merhaba)] struct Kedi;, the code the macro generates is the same as the impl you'd write by hand. We can see the same idea in a single file with macro_rules!:
trait Merhaba {
fn merhaba() -> String;
}
macro_rules! merhaba_turet {
($t:ident) => {
impl Merhaba for $t {
fn merhaba() -> String {
format!("Merhaba, ben {}", stringify!($t))
}
}
};
}
struct Kedi;
merhaba_turet!(Kedi);
The difference: a procedural macro reads the struct definition itself; here we give the name.
Exercise What derive produces
Write the Merhaba trait above and the merhaba_turet! macro. Apply the macro for struct Kedi; and struct Kopek;; print the merhaba() result of both types on separate lines.
Show example solution
trait Merhaba {
fn merhaba() -> String;
}
macro_rules! merhaba_turet {
($t:ident) => {
impl Merhaba for $t {
fn merhaba() -> String {
format!("Merhaba, ben {}", stringify!($t))
}
}
};
}
struct Kedi;
struct Kopek;
merhaba_turet!(Kedi);
merhaba_turet!(Kopek);
fn main() {
println!("{}", Kedi::merhaba());
println!("{}", Kopek::merhaba());
}
Test yourself
-
Why must procedural macros be in a separate crate?
- A security rule
- Because they're faster
- It isn't needed
- The compiler builds them first and runs them while compiling the crate that uses them
Show answer
The compiler builds them first and runs them while compiling the crate that uses them — The macro crate acts like a compiler plugin; it isn't ready yet while its own crate is being compiled.
-
Derive declaration
#[____(Merhaba)]
pub fn merhaba_turet(girdi: TokenStream) -> TokenStream {
todo!()
}
Show answer
#[proc_macro_derive(Merhaba)]
pub fn merhaba_turet(girdi: TokenStream) -> TokenStream {
todo!()
}
-
Macro kind and example
- derive
- #[derive(Serialize)]
- attribute
- #[tokio::main]
- fonksiyon benzeri
- sqlx::query!(..)
- bildirimsel
- macro_rules!
Next lesson: derive macro