0Pricing
Learn Rust Coding · Lesson

Procedural Macros: Derive, Function

Dive into procedural macros, including custom `#[derive]` macros and function-like macros, for more complex code generation.

Procedural Macros: Derive, Function is a free Learn Rust Coding lesson on CoddyKit — lesson 2 of 3. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Learn Rust Coding learning path, one of 3 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Intro to Procedural Macros

Welcome to the world of procedural macros! Unlike declarative macros (macro_rules!), procedural macros are like functions that operate on Rust code itself.

They take a TokenStream as input, process it, and return another TokenStream. This allows for much more complex code generation.

Types of Procedural Macros

Rust offers three main types of procedural macros:

  • Function-like macros: These look and act like regular function calls, e.g., my_macro!(...).
  • Derive macros: These allow you to implement traits automatically for structs and enums using #[derive(MyTrait)].
  • Attribute macros: These allow you to define custom attributes that can be applied to items, e.g., #[route("/path")].

The `proc-macro` Crate

Procedural macros must reside in their own special crate type. In your Cargo.toml, you declare it like this:

This tells Cargo that the crate contains macros that transform code at compile time.

[package]
name = "my_macros"
version = "0.1.0"
edition = "2021"

[lib]
proc-macro = true

Function-like Macros Explained

Function-like macros are defined with the #[proc_macro] attribute on a function that takes a proc_macro::TokenStream and returns one.

They're useful for custom DSLs (Domain Specific Languages) or complex code repetition that macro_rules! can't handle.

use proc_macro::TokenStream;

#[proc_macro]
pub fn my_function_macro(input: TokenStream) -> TokenStream {
    // Logic to transform input TokenStream
    // ...
    input // For example, return the input unchanged
}

Function-like Macro Example

Here's a conceptual example. A macro `greet_name!` that generates a print statement. The actual implementation would parse the input TokenStream to extract the name.

While the macro definition is complex, its usage is simple and powerful:

/* In 'my_macros/src/lib.rs' */
use proc_macro::TokenStream;

#[proc_macro]
pub fn greet_name(input: TokenStream) -> TokenStream {
    // Imagine parsing 'input' to get a name like 'World'
    // and generating: println!("Hello, World!");
    "println!(\"Hello, {}!\", \"CoddyKit\");".parse().unwrap()
}

/* In 'my_app/src/main.rs' */
use my_macros::greet_name;

fn main() {
    greet_name!("CoddyKit");
}

Understanding `#[derive]` Macros

Derive macros are the most common type. They allow you to automatically implement a trait for a struct or enum.

When you write #[derive(Debug)], a macro runs at compile time to generate the necessary code for your type to implement the Debug trait.

Implementing a `#[derive]` (Concept)

Custom derive macros are typically built using libraries like syn (for parsing Rust code into an AST) and quote (for generating Rust code from an AST).

You'd define a function annotated with #[proc_macro_derive(MyTrait)] that takes a TokenStream representing the struct/enum and returns the generated trait implementation.

/* In 'my_macros/src/lib.rs' */
use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput};
use quote::quote;

#[proc_macro_derive(MyDebug)] // MyDebug is the trait name
pub fn my_debug_derive(input: TokenStream) -> TokenStream {
    let ast = parse_macro_input!(input as DeriveInput);
    let name = &ast.ident;

    let expanded = quote! {
        impl std::fmt::Debug for #name {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                f.debug_struct(stringify!(#name)).finish()
            }
        }
    };
    expanded.into()
}

Using `#[derive(Debug)]` Example

Let's see a practical example of a derive macro you've likely used: Debug. By adding #[derive(Debug)] to our Point struct, Rust automatically implements the Debug trait, allowing us to print its contents nicely.

#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p = Point { x: 10, y: 20 };
    println!("My point is: {:?}", p);
}

Attribute Macros: `#[my_attribute]`

Attribute macros are similar to derive macros but can be applied to any item (functions, structs, modules, etc.). They take the item they are attached to, plus any arguments in parentheses, as input.

Common uses include web framework routing (e.g., #[get("/users")]) or test setup (e.g., #[test]).

/* In 'my_macros/src/lib.rs' */
use proc_macro::TokenStream;

#[proc_macro_attribute]
pub fn log_calls(attr: TokenStream, item: TokenStream) -> TokenStream {
    // Imagine adding println! statements to 'item'
    item
}

/* In 'my_app/src/main.rs' */
use my_macros::log_calls;

#[log_calls("entry_point")]
fn my_function() {
    println!("Inside my_function");
}

fn main() {
    my_function();
}

When to Use Which Macro

  • Function-like macros: For custom mini-languages or complex transformations of arbitrary code blocks.
  • Derive macros: For automatically implementing traits on structs and enums.
  • Attribute macros: For modifying or generating code around specific items (functions, structs) based on custom attributes.

Each type serves a unique purpose in extending Rust's syntax and capabilities.

Quick Check: Macro Types

Which of the following statements about procedural macros are TRUE?

Recap: Code Generation Power

You've explored the advanced world of procedural macros!

  • We learned about function-like macros for custom syntax.
  • Understood how #[derive] macros automate trait implementations.
  • Briefly touched upon attribute macros for item-level code generation.

Procedural macros are a powerful tool for reducing boilerplate and extending Rust's capabilities, enabling you to write code that writes code!

Frequently asked questions

Is the “Procedural Macros: Derive, Function” lesson free?

Yes — the full text of “Procedural Macros: Derive, Function” is free to read here on the web, and the Learn Rust Coding course includes 3 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Learn Rust Coding course, upgrade to CoddyKit PRO.

What will I learn in “Procedural Macros: Derive, Function”?

Dive into procedural macros, including custom `#[derive]` macros and function-like macros, for more complex code generation. You practise Learn Rust Coding with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Learn Rust Coding?

No prior experience is required. Learn Rust Coding on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 3, so you can start here or from the beginning and move at your own pace.

How long does the “Procedural Macros: Derive, Function” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Learn Rust Coding lesson?

Yes. Every Learn Rust Coding lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Declarative Macros (`macro_rules!`)
  2. Procedural Macros: Derive, Function
  3. Interacting with Unsafe Rust
← Back to Learn Rust Coding