proc_macro crate
- Rust course
- Advanced
- Lesson 48/81
- 4 min
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.
Practice 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.
Starter code
fn main() {
// buraya yaz
}Sample 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());
}Expected output
Merhaba, ben Kedi Merhaba, ben Kopek
Questions and answers
Review the lesson questions together with their answers. To test yourself, solve the lesson interactively.
1 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 Correct answer
Explanation: The macro crate acts like a compiler plugin; it isn't ready yet while its own crate is being compiled.
2 Derive declaration
#[____(Merhaba)]
pub fn merhaba_turet(girdi: TokenStream) -> TokenStream {
todo!()
}
#[proc_macro_derive(Merhaba)]
pub fn merhaba_turet(girdi: TokenStream) -> TokenStream {
todo!()
}
Explanation: #[proc_macro_derive(Name)] defines a derive macro.
3 Macro kind and example
| Term | Meaning |
|---|---|
derive |
#[derive(Serialize)] |
attribute |
#[tokio::main] |
fonksiyon benzeri |
sqlx::query!(..) |
bildirimsel |
macro_rules! |