Rust-skills rust-macro

Macro and procedural metaprogramming expert covering macro_rules!, derive macros, proc-macros, compile-time computation, and code generation patterns.

install
source · Clone the upstream repo
git clone https://github.com/huiali/rust-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/huiali/rust-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.codex/skills/rust-macro" ~/.claude/skills/huiali-rust-skills-rust-macro && rm -rf "$T"
manifest: .codex/skills/rust-macro/SKILL.md
source content

Macros vs Generics

DimensionMacrosGenerics
FlexibilityCode transformationType abstraction
Compile costIncremental-friendlyMonomorphization overhead
Error messagesCan be crypticClear
DebuggingDebug expanded codeDirect debugging
Use caseReduce boilerplateGeneric algorithms

Solution Patterns

Pattern 1: Declarative Macro (macro_rules!)

// 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

// 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

#[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

#[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

SyntaxMeaning
$()
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
// 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

# Expand macros
cargo expand

# Expand specific function
cargo expand my_module::my_function

# Expand tests
cargo expand --test test_name

Step 4: Test Thoroughly

#[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

CratePurpose
synParse Rust syntax
quoteGenerate Rust code
proc-macro2Token manipulation
derive-moreCommon derive macros
darlingParse macro attributes

Best Practices

PracticeReason
Try generics firstSafer, easier to debug
Keep macros simpleComplex macros hard to maintain
Document macrosUsers need to understand expansion
Test expansionEnsure correctness
Use cargo expandVisualize 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

# 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

// ❌ 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

// ✅ 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

// ❌ 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 - 完整中文版本,包含所有内容