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.mdsource content
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!)
// 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
| Syntax | Meaning |
|---|---|
| Match zero or more |
| Comma-separated |
| At least one |
| Type matcher |
| Expression matcher |
| Pattern matcher |
| Identifier matcher |
| Path matcher |
| 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
| 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
# 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 - 完整中文版本,包含所有内容