Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

What a proc macro returns

A derive macro receives the annotated item as a TokenStream and returns a TokenStream. But what exactly should that returned stream contain?

Derive macros add code

Unlike attribute macros, a derive macro does not replace the original item. The struct or enum you annotate with #[derive(...)] is preserved as-is. The token stream you return is appended after the original item.

This means:

  • If you return an empty token stream, the item is unchanged. This is valid — the macro simply has no effect.
  • If you return an impl block, that implementation is added alongside the original item.
  • If you return malformed tokens, compilation fails.
#![allow(unused)]
fn main() {
use proc_macro::TokenStream;

#[proc_macro_derive(NoOp)]
pub fn no_op(_input: TokenStream) -> TokenStream {
    // Return nothing — the item is preserved, no code is added.
    TokenStream::new()
}
}

The returned tokens must be valid Rust

The compiler will try to parse and compile whatever tokens you return. If they aren’t valid Rust, you’ll get a compilation error — but the error will point at the #[derive(...)] invocation, not at the generated code. This can be confusing.

cargo expand is your best friend here. When the generated code doesn’t compile, expand it to see exactly what was produced.

A working example

Here’s a derive macro that generates a simple method:

#![allow(unused)]
fn main() {
use proc_macro::TokenStream;

#[proc_macro_derive(Hello)]
pub fn hello(_input: TokenStream) -> TokenStream {
    // For now, hardcode the struct name.
    // We'll learn how to read it from the input later.
    "impl Greeting { fn hello() -> &'static str { \"hello!\" } }"
        .parse()
        .unwrap()
}
}

This works, but it’s fragile — the struct name is hardcoded. In the next exercises, you’ll learn how to parse the input to extract the actual name.

Returning tokens from a string

The simplest way to produce a TokenStream is to parse it from a string using the str::parse() method, as shown above. This is fine for quick experiments, but it gives you no compile-time checks on the generated code — any mistake in the string only shows up when someone uses the macro.

Later, we’ll use the quote! macro, which provides a much better way to construct token streams. We’ll also see how to report errors properly using compile_error! and syn::Error, instead of relying on confusing error messages from malformed tokens.

Exercise

The exercise for this section is located in 03_basics/02_what_a_proc_macro_returns

Write a derive macro that generates a method returning the name of the type as a string. For now, you can hardcode the name.