CodeBlanks Logo Icon CodeBlanks

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.

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

main.rs
fn main() {
    // buraya yaz
}

Sample solution

main.rs
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

Question
#[____(Merhaba)]
pub fn merhaba_turet(girdi: TokenStream) -> TokenStream {
    todo!()
}
Answer
#[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!

All lessons in Advanced