home / skills / huiali / rust-skills / rust-macro

rust-macro skill

/skills/rust-macro

This skill helps engineers harness Rust macros and proc-macros to reduce boilerplate, improve compile-time checks, and generate robust, reusable code patterns.

npx playbooks add skill huiali/rust-skills --skill rust-macro

Review the files below or copy the command above to add this skill to your agents.

Files (4)
SKILL.md
7.9 KB
---
name: rust-macro
description: Macro and procedural metaprogramming expert covering macro_rules!, derive macros, proc-macros, compile-time computation, and code generation patterns.
metadata:
  triggers:
    - macro
    - derive
    - proc-macro
    - macro_rules
    - metaprogramming
    - code generation
    - compile-time
---


## Macros vs Generics

| Dimension | Macros | Generics |
|-----------|--------|----------|
| Flexibility | Code transformation | Type abstraction |
| Compile cost | Incremental-friendly | Monomorphization overhead |
| Error messages | Can be cryptic | Clear |
| Debugging | Debug expanded code | Direct debugging |
| Use case | Reduce boilerplate | Generic algorithms |


## Solution Patterns

### Pattern 1: Declarative Macro (macro_rules!)

```rust
// Basic structure
macro_rules! my_vec {
    // Empty case
    () => {
        Vec::new()
    };
    // List of elements
    ($($elem:expr),* $(,)?) => {{
        let mut v = Vec::new();
        $(
            v.push($elem);
        )*
        v
    }};
    // Repeated element
    ($elem:expr; $n:expr) => {
        vec![$elem; $n]
    };
}

// Usage
let v1 = my_vec![];
let v2 = my_vec![1, 2, 3];
let v3 = my_vec![0; 10];
```

### Pattern 2: Derive Macro

```rust
// In a separate proc-macro crate
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};

#[proc_macro_derive(Builder)]
pub fn derive_builder(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;
    let builder_name = format!("{}Builder", name);
    let builder_ident = syn::Ident::new(&builder_name, name.span());

    let fields = match &input.data {
        syn::Data::Struct(data) => &data.fields,
        _ => panic!("Builder only works on structs"),
    };

    let field_names: Vec<_> = fields.iter()
        .filter_map(|f| f.ident.as_ref())
        .collect();

    let field_types: Vec<_> = fields.iter()
        .map(|f| &f.ty)
        .collect();

    let expanded = quote! {
        pub struct #builder_ident {
            #(#field_names: Option<#field_types>),*
        }

        impl #builder_ident {
            pub fn new() -> Self {
                Self {
                    #(#field_names: None),*
                }
            }

            #(
                pub fn #field_names(mut self, value: #field_types) -> Self {
                    self.#field_names = Some(value);
                    self
                }
            )*

            pub fn build(self) -> Result<#name, String> {
                Ok(#name {
                    #(
                        #field_names: self.#field_names
                            .ok_or_else(|| format!("Field {} not set", stringify!(#field_names)))?
                    ),*
                })
            }
        }

        impl #name {
            pub fn builder() -> #builder_ident {
                #builder_ident::new()
            }
        }
    };

    expanded.into()
}
```

### Pattern 3: Function-like Proc Macro

```rust
#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {
    let sql_string = input.to_string();

    // Parse and validate SQL at compile time
    validate_sql(&sql_string);

    // Generate code
    quote! {
        QueryBuilder::raw(#sql_string)
    }.into()
}

// Usage:
let query = sql!("SELECT * FROM users WHERE id = ?");
```

### Pattern 4: Attribute Macro

```rust
#[proc_macro_attribute]
pub fn cached(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let input = parse_macro_input!(item as ItemFn);
    let fn_name = &input.sig.ident;
    let fn_body = &input.block;

    let expanded = quote! {
        fn #fn_name() -> Result {
            use std::sync::OnceLock;
            static CACHE: OnceLock<Result> = OnceLock::new();

            CACHE.get_or_init(|| {
                #fn_body
            }).clone()
        }
    };

    expanded.into()
}

// Usage:
#[cached]
fn expensive_computation() -> String {
    // ...
}
```


## Repetition Patterns

| Syntax | Meaning |
|--------|---------|
| `$()` | Match zero or more |
| `$($x),*` | Comma-separated |
| `$($x),+` | At least one |
| `$x:ty` | Type matcher |
| `$x:expr` | Expression matcher |
| `$x:pat` | Pattern matcher |
| `$x:ident` | Identifier matcher |
| `$x:path` | Path matcher |
| `$x:tt` | Token tree matcher |

```rust
// Example: multiple matchers
macro_rules! create_struct {
    ($name:ident { $($field:ident: $type:ty),* }) => {
        struct $name {
            $($field: $type),*
        }
    };
}

create_struct!(User {
    id: u64,
    name: String,
    email: String
});
```


## Workflow

### Step 1: Consider Alternatives

```
Need to reduce duplication?
  → Can generics solve it? Prefer generics
  → Need syntax transformation? Use macros
  → Need to inspect types? Derive macro
  → Need attribute? Attribute macro
```

### Step 2: Choose Macro Type

```
Declarative (macro_rules!)?
  ✅ Simple pattern matching
  ✅ Quick to write
  ❌ Limited power

Procedural (proc-macro)?
  ✅ Full AST access
  ✅ Complex transformations
  ❌ Separate crate needed
  ❌ Longer compile times
```

### Step 3: Debug Expansion

```bash
# Expand macros
cargo expand

# Expand specific function
cargo expand my_module::my_function

# Expand tests
cargo expand --test test_name
```

### Step 4: Test Thoroughly

```rust
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_macro_expansion() {
        // Test generated code
        let result = my_macro!(input);
        assert_eq!(result, expected);
    }
}
```


## Common Crates

| Crate | Purpose |
|-------|---------|
| **syn** | Parse Rust syntax |
| **quote** | Generate Rust code |
| **proc-macro2** | Token manipulation |
| **derive-more** | Common derive macros |
| **darling** | Parse macro attributes |


## Best Practices

| Practice | Reason |
|----------|--------|
| Try generics first | Safer, easier to debug |
| Keep macros simple | Complex macros hard to maintain |
| Document macros | Users need to understand expansion |
| Test expansion | Ensure correctness |
| Use cargo expand | Visualize macro output |


## Review Checklist

When reviewing macro code:

- [ ] Could this be solved with generics instead?
- [ ] Macro expansion documented with examples
- [ ] Error messages are helpful
- [ ] Edge cases tested
- [ ] Hygiene respected (no accidental captures)
- [ ] Used cargo expand to verify output
- [ ] Compile-time overhead acceptable
- [ ] No unnecessary proc-macro dependency


## Verification Commands

```bash
# Expand macros
cargo expand

# Expand specific module
cargo expand path::to::module

# Check proc-macro crate
cargo check -p my-proc-macro

# Test expansion
cargo test --all-features
```


## Common Pitfalls

### 1. Hygiene Violations

**Symptom**: Unexpected variable captures

```rust
// ❌ Bad: name clash risk
macro_rules! bad_macro {
    ($x:expr) => {{
        let result = $x;  // 'result' might clash
        result
    }};
}

// ✅ Good: use unique names
macro_rules! good_macro {
    ($x:expr) => {{
        let __macro_result = $x;
        __macro_result
    }};
}
```

### 2. Complex Error Messages

**Symptom**: Users don't understand macro errors

```rust
// ✅ Good: helpful error messages
macro_rules! require_trait {
    ($t:ty) => {
        const _: fn() = || {
            fn assert_impl<T: MyTrait>() {}
            assert_impl::<$t>();
        };
    };
}
```

### 3. Proc Macro Compile Time

**Symptom**: Slow incremental builds

```rust
// ❌ Avoid: heavy proc macros for simple tasks
#[derive(HeavyProcMacro)]
struct Simple {
    field: String,
}

// ✅ Better: manual impl or simpler derive
impl Simple {
    // Manual implementation
}
```


## Related Skills

- **rust-coding** - Naming macro conventions
- **rust-performance** - Macro compile-time cost
- **rust-type-driven** - When generics suffice
- **rust-error** - Error handling in macros
- **rust-testing** - Testing macro expansion


## Localized Reference

- **Chinese version**: [SKILL_ZH.md](./SKILL_ZH.md) - 完整中文版本,包含所有内容

Overview

This skill is a macro and procedural metaprogramming expert for Rust, covering macro_rules!, derive macros, function-like proc-macros, attribute macros, compile-time validation, and common code-generation patterns. It helps choose between macros and generics, design hygienic expansions, and optimize compile-time cost while keeping generated code testable and readable.

How this skill works

The skill inspects code and design intent to recommend an appropriate macro type and pattern, generates example implementations, and validates common pitfalls (hygiene, error messages, compile-time cost). It explains repetition matchers, shows expansion tooling, and provides templates for declarative macros, derives, function-like and attribute proc-macros. It also outlines verification commands and review checklists.

When to use it

  • You need syntax transformation that generics cannot express
  • You want to reduce repetitive boilerplate across many types or patterns
  • You need to inspect or generate code based on a type’s AST (derive macro)
  • You want compile-time validation or embedding of domain-specific languages
  • You need to add behavior to functions or items via attributes

Best practices

  • Prefer generics for type abstraction before using macros
  • Keep macros as small and focused as possible
  • Use unique internal identifiers to preserve hygiene and avoid captures
  • Document and test expanded output with cargo expand and unit tests
  • Avoid heavy proc-macros for trivial tasks to reduce compile-time overhead

Example use cases

  • Declarative macro to construct collections with ergonomic syntax (like my_vec!)
  • Derive macro to implement a Builder pattern for structs with compile-time checks
  • Function-like proc-macro that validates and embeds domain-specific strings (e.g., SQL) at compile time
  • Attribute macro to cache expensive function results with OnceLock or memoization
  • Generate repetitive boilerplate for FFI bindings or serialization implementations

FAQ

When should I prefer macro_rules! over a proc-macro?

Use macro_rules! for straightforward pattern-based transformations that don’t need full AST access; it’s quick to write and incremental-friendly. Choose proc-macros when you need complex parsing, type inspection, or richer code generation.

How do I debug confusing macro errors?

Expand macros with cargo expand to see generated code, add focused unit tests for generated behavior, and craft explicit compile-time asserts or helpful panic messages in proc-macros to guide users.