Rust巨集元程式設計實戰:從宣告巨集到過程巨集的5種生產模式

编程语言

Rust巨集,為什麼總是寫完就忘

你寫了個macro_rules!,三天後自己都看不懂匹配規則;你想給結構體自動實作Serialize,發現derive巨集的proc-macrocrate配置一堆坑;你用屬性巨集做日誌裝飾器,編譯報錯一堆TokenStream不匹配;你想用過程巨集生成程式碼,發現syn解析和quote拼接的偵錯全靠cargo expand。2026年,Rust巨集系統已經提供了宣告巨集、三種過程巨集(derive/attribute/function-like)、以及成熟的syn+quote生態——但巨集程式設計這件事,從來不是會寫就能寫對的

本文將從宣告巨集出發,帶你完成macro_rules! DSL→Derive巨集自動實作→屬性巨集裝飾器→函式式過程巨集→syn/quote生產級程式碼生成的5種實戰模式,讓Rust巨集從"能編譯"變成"能維護"。


核心概念

概念 說明
macro_rules! 宣告巨集,基於模式匹配的程式碼模板,編譯時展開
Declarative Macro 宣告巨集的別稱,用macro_rules!定義
Procedural Macro 過程巨集,接收TokenStream輸入,回傳TokenStream輸出
Derive Macro 派生巨集,為結構體/列舉自動實作trait
Attribute Macro 屬性巨集,標註在item上,可修改或替換被標註的item
Function-like Proc Macro 函式式過程巨集,像函式一樣呼叫mac!()
TokenStream Token流,過程巨集的輸入輸出型別
syn Rust原始碼解析庫,將TokenStream解析為AST
quote 程式碼生成庫,將Rust程式碼模板轉為TokenStream
cargo expand 工具,展開巨集檢視生成程式碼
Span 原始碼位置資訊,用於錯誤報告定位

巨集展開流程

巨集展開流程:
1. 編譯器遇到巨集呼叫 mac!(...)
2. 宣告巨集:按模式匹配arm,替換為模板程式碼
   過程巨集:將輸入轉為TokenStream
3. 過程巨集解析TokenStream為AST(syn::parse)
4. 對AST進行變換/生成新程式碼
5. 將生成程式碼轉為TokenStream(quote::quote!)
6. 編譯器將展開結果編譯為正常Rust程式碼
7. 巨集展開後的程式碼參與型別檢查和借用檢查

問題分析:Rust巨集開發的5大挑戰

  1. 宣告巨集模式匹配難以偵錯macro_rules!的匹配規則晦澀,$($x:tt),*這種重複匹配寫完就忘,錯誤資訊不友善
  2. 過程巨集crate配置繁瑣:derive/attribute/function-like巨集必須放在獨立的proc-macro = truecrate中,與主crate分離
  3. TokenStream偵錯全靠cargo expand:過程巨集內部println!看不到輸出,只能用cargo expandeprintln!到stderr
  4. syn解析複雜語法容易出錯:泛型引數、生命週期、where子句的解析需要處理大量邊界情況
  5. 巨集生成的程式碼缺乏IDE支援:巨集展開後的程式碼沒有補全、跳轉、重構支援,維護成本高

分步實操:5種生產級巨集模式

模式1:宣告巨集macro_rules!與DSL建立

macro_rules! define_enum_with_display {
    (
        $(#[$meta:meta])*
        $vis:vis enum $name:ident {
            $(
                $(#[$variant_meta:meta])*
                $variant:ident = $value:expr
            ),* $(,)?
        }
    ) => {
        $(#[$meta])*
        $vis enum $name {
            $(
                $(#[$variant_meta])*
                $variant = $value,
            )*
        }

        impl std::fmt::Display for $name {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                match self {
                    $(
                        $name::$variant => write!(f, stringify!($variant)),
                    )*
                }
            }
        }

        impl $name {
            pub fn values() -> Vec<Self> {
                vec![$($name::$variant),*]
            }

            pub fn from_str(s: &str) -> Option<Self> {
                match s {
                    $(
                        stringify!($variant) => Some($name::$variant),
                    )*
                    _ => None,
                }
            }
        }
    };
}

define_enum_with_display! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    pub enum HttpStatus {
        Ok = 200,
        Created = 201,
        BadRequest = 400,
        Unauthorized = 401,
        NotFound = 404,
        InternalServerError = 500,
    }
}
macro_rules! builder_pattern {
    (
        $(#[$struct_meta:meta])*
        $vis:vis struct $name:ident {
            $(
                $(#[$field_meta:meta])*
                $field:ident : $ty:ty
            ),* $(,)?
        }
    ) => {
        $(#[$struct_meta])*
        $vis struct $name {
            $(
                $(#[$field_meta])*
                $field: Option<$ty>,
            )*
        }

        impl $name {
            pub fn builder() -> ${concat($name, Builder)} {
                ${concat($name, Builder)} {
                    $(
                        $field: None,
                    )*
                }
            }
        }

        pub struct ${concat($name, Builder)} {
            $(
                $field: Option<$ty>,
            )*
        }

        impl ${concat($name, Builder)} {
            $(
                pub fn $field(mut self, value: $ty) -> Self {
                    self.$field = Some(value);
                    self
                }
            )*

            pub fn build(self) -> Result<$name, String> {
                let missing: Vec<&str> = vec![
                    $(
                        if self.$field.is_none() { stringify!($field) } else { "" },
                    )*
                ].into_iter().filter(|s| !s.is_empty()).collect();

                if !missing.is_empty() {
                    return Err(format!("Missing required fields: {}", missing.join(", ")));
                }

                Ok($name {
                    $(
                        $field: self.$field,
                    )*
                })
            }
        }
    };
}

builder_pattern! {
    pub struct User {
        name: String,
        email: String,
        age: u32,
    }
}

模式2:Derive巨集自動實作Trait

# Cargo.toml (proc-macro crate)
[package]
name = "my_derive"
version = "0.1.0"
edition = "2021"

[lib]
proc-macro = true

[dependencies]
syn = "2.0"
quote = "1.0"
proc-macro2 = "1.0"
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput, Data, Fields, GenericParam, Ident};

#[proc_macro_derive(CustomDebug, attributes(debug_name))]
pub fn derive_custom_debug(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;

    let generics = &input.generics;
    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();

    let debug_impl = match &input.data {
        Data::Struct(data) => {
            match &data.fields {
                Fields::Named(fields) => {
                    let field_debug: Vec<_> = fields.named.iter().map(|f| {
                        let field_name = &f.ident;
                        let field_name_str = field_name.as_ref().unwrap().to_string();
                        quote! {
                            .field(#field_name_str, &self.#field_name)
                        }
                    }).collect();

                    let struct_name_str = name.to_string();
                    quote! {
                        impl #impl_generics std::fmt::Debug for #name #ty_generics #where_clause {
                            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                                f.debug_struct(#struct_name_str)
                                    #(#field_debug)*
                                    .finish()
                            }
                        }
                    }
                }
                Fields::Unnamed(fields) => {
                    let field_debug: Vec<_> = fields.unnamed.iter().enumerate().map(|(i, _)| {
                        let idx = syn::Index::from(i);
                        quote! {
                            .field(&self.#idx)
                        }
                    }).collect();

                    let struct_name_str = name.to_string();
                    quote! {
                        impl #impl_generics std::fmt::Debug for #name #ty_generics #where_clause {
                            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                                f.debug_tuple(#struct_name_str)
                                    #(#field_debug)*
                                    .finish()
                            }
                        }
                    }
                }
                Fields::Unit => {
                    let struct_name_str = name.to_string();
                    quote! {
                        impl #impl_generics std::fmt::Debug for #name #ty_generics #where_clause {
                            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                                write!(f, #struct_name_str)
                            }
                        }
                    }
                }
            }
        }
        Data::Enum(data) => {
            let variant_debug: Vec<_> = data.variants.iter().map(|v| {
                let variant_name = &v.ident;
                let variant_name_str = variant_name.to_string();
                match &v.fields {
                    Fields::Named(fields) => {
                        let field_names: Vec<_> = fields.named.iter().map(|f| &f.ident).collect();
                        let field_strs: Vec<_> = field_names.iter().map(|f| f.as_ref().unwrap().to_string()).collect();
                        quote! {
                            #name::#variant_name { #(#field_names),* } => {
                                f.debug_struct(#variant_name_str)
                                    #( .field(#field_strs, #field_names) )*
                                    .finish()
                            }
                        }
                    }
                    Fields::Unnamed(fields) => {
                        let field_names: Vec<_> = fields.unnamed.iter().enumerate().map(|(i, _)| {
                            Ident::new(&format!("f{}", i), v.ident.span())
                        }).collect();
                        quote! {
                            #name::#variant_name(#(#field_names),*) => {
                                f.debug_tuple(#variant_name_str)
                                    #( .field(#field_names) )*
                                    .finish()
                            }
                        }
                    }
                    Fields::Unit => {
                        quote! {
                            #name::#variant_name => write!(f, #variant_name_str)
                        }
                    }
                }
            }).collect();

            quote! {
                impl #impl_generics std::fmt::Debug for #name #ty_generics #where_clause {
                    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                        match self {
                            #(#variant_debug),*
                        }
                    }
                }
            }
        }
        Data::Union(_) => {
            syn::Error::new_spanned(name, "CustomDebug does not support unions")
                .to_compile_error()
                .into()
        }
    };

    debug_impl.into()
}
use my_derive::CustomDebug;

#[derive(CustomDebug)]
pub struct ApiResponse {
    pub status: u16,
    pub message: String,
    pub data: Option<serde_json::Value>,
}

#[derive(CustomDebug)]
pub enum Result<T, E> {
    Ok(T),
    Err(E),
}

模式3:屬性巨集實作日誌/驗證/快取裝飾器

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemFn, FnArg, Pat, Signature, Visibility};

#[proc_macro_attribute]
pub fn traced(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let input = parse_macro_input!(item as ItemFn);
    let ItemFn { attrs, vis, sig, block, .. } = input;

    let fn_name = &sig.ident.to_string();
    let fn_name_lit = syn::LitStr::new(fn_name, sig.ident.span());

    let expanded = quote! {
        #(#attrs)*
        #vis #sig {
            let __start = std::time::Instant::now();
            tracing::info!(function = #fn_name_lit, "→ entering");
            let __result = #block;
            let __elapsed = __start.elapsed();
            tracing::info!(
                function = #fn_name_lit,
                elapsed_ms = __elapsed.as_millis() as u64,
                "← exiting"
            );
            __result
        }
    };

    expanded.into()
}

#[proc_macro_attribute]
pub fn validate_args(attr: TokenStream, item: TokenStream) -> TokenStream {
    let input = parse_macro_input!(item as ItemFn);
    let attr_args: Vec<String> = attr.to_string()
        .split(',')
        .map(|s| s.trim().to_string())
        .collect();

    let ItemFn { attrs, vis, sig, block, .. } = input;
    let fn_name = &sig.ident;

    let validations: Vec<_> = sig.inputs.iter().filter_map(|arg| {
        if let FnArg::Typed(pat_type) = arg {
            if let Pat::Ident(pat_ident) = &*pat_type.pat {
                let param_name = pat_ident.ident.to_string();
                if attr_args.is_empty() || attr_args.contains(&param_name) {
                    let ident = &pat_ident.ident;
                    return Some(quote! {
                        if #ident.is_empty() {
                            return Err(format!("Parameter '{}' must not be empty", #param_name));
                        }
                    });
                }
            }
        }
        None
    }).collect();

    let return_type = match &sig.output {
        syn::ReturnType::Type(_, ty) => quote! { #ty },
        syn::ReturnType::Default => quote! { () },
    };

    let inputs = &sig.inputs;
    let output = &sig.output;

    let expanded = quote! {
        #(#attrs)*
        #vis fn #fn_name(#inputs) #output {
            #(#validations)*
            #block
        }
    };

    expanded.into()
}

#[proc_macro_attribute]
pub fn cached(attr: TokenStream, item: TokenStream) -> TokenStream {
    let ttl_secs: u64 = if attr.is_empty() {
        60
    } else {
        attr.to_string().parse().unwrap_or(60)
    };

    let input = parse_macro_input!(item as ItemFn);
    let ItemFn { attrs, vis, sig, block, .. } = input;
    let fn_name = &sig.ident;
    let fn_name_str = fn_name.to_string();

    let expanded = quote! {
        #(#attrs)*
        #vis #sig {
            static CACHE: std::sync::OnceLock<std::sync::Mutex<(std::time::Instant, Option<_>)>> = std::sync::OnceLock::new();
            let cache = CACHE.get_or_init(|| std::sync::Mutex::new((std::time::Instant::now(), None)));
            let mut guard = cache.lock().unwrap();
            let now = std::time::Instant::now();
            if let (created, Some(ref val)) = guard.0 {
                if now.duration_since(created).as_secs() < #ttl_secs {
                    return val.clone();
                }
            }
            drop(guard);
            let result = #block;
            let mut guard = cache.lock().unwrap();
            *guard = (std::time::Instant::now(), Some(result.clone()));
            result
        }
    };

    expanded.into()
}
use my_macros::{traced, validate_args, cached};

#[traced]
async fn fetch_user(user_id: &str) -> Result<User, AppError> {
    let client = reqwest::Client::new();
    let resp = client.get(&format!("/api/users/{}", user_id)).send().await?;
    Ok(resp.json().await?)
}

#[validate_args(name, email)]
fn create_user(name: String, email: String, age: u32) -> Result<User, String> {
    Ok(User { name, email, age })
}

#[cached(300)]
fn get_config() -> AppConfig {
    load_config_from_file()
}

模式4:函式式過程巨集實作SQL查詢建構器

use proc_macro::TokenStream;
use quote::quote;
use syn::parse::{Parse, ParseStream};
use syn::{Ident, LitStr, Token, Result as SynResult};

struct SqlQuery {
    table: Ident,
    columns: Vec<Ident>,
    where_clause: Option<LitStr>,
}

impl Parse for SqlQuery {
    fn parse(input: ParseStream) -> SynResult<Self> {
        let table: Ident = input.parse()?;
        let mut columns = vec![table.clone()];

        if input.peek(Token![.]) {
            input.parse::<Token![.]>()?;
            let method: Ident = input.parse()?;
            if method != "select" {
                return Err(syn::Error::new(method.span(), "Expected .select()"));
            }
            input.parse::<Token![()]>();
            let content;
            syn::parenthesized!(content in input);
            columns.clear();
            loop {
                let col: Ident = content.parse()?;
                columns.push(col);
                if content.peek(Token![,]) {
                    content.parse::<Token![,]>()?;
                } else {
                    break;
                }
            }
        }

        let where_clause = if input.peek(Token![.]) {
            input.parse::<Token![.]>()?;
            let method: Ident = input.parse()?;
            if method == "where_clause" {
                input.parse::<Token![()]>();
                let content;
                syn::parenthesized!(content in input);
                Some(content.parse()?)
            } else {
                None
            }
        } else {
            None
        };

        Ok(SqlQuery { table, columns, where_clause })
    }
}

#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {
    let query = match syn::parse::<SqlQuery>(input) {
        Ok(q) => q,
        Err(e) => return e.to_compile_error().into(),
    };

    let table_name = query.table.to_string();
    let column_names: Vec<String> = query.columns.iter().map(|c| c.to_string()).collect();
    let column_refs: Vec<_> = column_names.iter().map(|c| c.as_str()).collect();

    let struct_name = Ident::new(
        &format!("{}Row", to_pascal_case(&table_name)),
        query.table.span(),
    );

    let field_names: Vec<Ident> = column_names.iter()
        .map(|c| Ident::new(&to_snake_case(c), query.table.span()))
        .collect();

    let field_types: Vec<_> = column_names.iter().map(|_| quote! { String }).collect();

    let select_clause = if column_names.len() == 1 && column_names[0] == table_name {
        "*".to_string()
    } else {
        column_names.join(", ")
    };

    let where_part = match &query.where_clause {
        Some(w) => format!(" WHERE {}", w.value()),
        None => String::new(),
    };

    let sql_string = format!("SELECT {} FROM {}{}", select_clause, table_name, where_part);

    let expanded = quote! {
        struct #struct_name {
            #(pub #field_names: #field_types,)*
        }

        impl #struct_name {
            pub fn sql() -> &'static str {
                #sql_string
            }

            pub fn from_row(row: &sqlx::postgres::PgRow) -> Result<Self, sqlx::Error> {
                Ok(Self {
                    #(
                        #field_names: row.try_get(stringify!(#field_names))?,
                    )*
                })
            }
        }
    };

    expanded.into()
}

fn to_pascal_case(s: &str) -> String {
    s.split('_')
        .map(|word| {
            let mut chars = word.chars();
            match chars.next() {
                None => String::new(),
                Some(c) => c.to_uppercase().collect::<String>() + &chars.as_str().to_lowercase(),
            }
        })
        .collect()
}

fn to_snake_case(s: &str) -> String {
    s.to_lowercase()
}
use my_macros::sql;

sql!(users.select(id, name, email).where_clause("active = true"));

sql!(orders.select(id, user_id, total));

sql!(products);

模式5:生產級程式碼生成與syn/quote

use proc_macro::TokenStream;
use quote::quote;
use syn::{
    parse_macro_input, DeriveInput, Data, Fields, Attribute, Meta, Lit, NestedMeta,
    GenericParam, LifetimeParam, TypeParam, ConstParam,
};

#[proc_macro_derive(ApiEndpoint, attributes(api_route, api_method, api_auth))]
pub fn derive_api_endpoint(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;

    let route = get_string_attr(&input.attrs, "api_route").unwrap_or_else(|| "/".to_string());
    let method = get_string_attr(&input.attrs, "api_method").unwrap_or_else(|| "GET".to_string());
    let auth_required = get_bool_attr(&input.attrs, "api_auth").unwrap_or(false);

    let request_type = Ident::new(&format!("{}Request", name), name.span());
    let response_type = Ident::new(&format!("{}Response", name), name.span());
    let handler_name = Ident::new(&format!("handle_{}", to_snake_case(&name.to_string())), name.span());

    let (request_fields, response_fields) = match &input.data {
        Data::Struct(data) => {
            match &data.fields {
                Fields::Named(fields) => {
                    let req_fields: Vec<_> = fields.named.iter()
                        .filter(|f| !has_attr(&f.attrs, "response_only"))
                        .map(|f| {
                            let fname = f.ident.as_ref().unwrap();
                            let ftype = &f.ty;
                            quote! { pub #fname: #ftype }
                        }).collect();

                    let resp_fields: Vec<_> = fields.named.iter()
                        .filter(|f| !has_attr(&f.attrs, "request_only"))
                        .map(|f| {
                            let fname = f.ident.as_ref().unwrap();
                            let ftype = &f.ty;
                            quote! { pub #fname: #ftype }
                        }).collect();

                    (req_fields, resp_fields)
                }
                _ => (vec![], vec![]),
            }
        }
        _ => (vec![], vec![]),
    };

    let method_ident = match method.to_uppercase().as_str() {
        "POST" => quote! { axum::routing::post },
        "PUT" => quote! { axum::routing::put },
        "DELETE" => quote! { axum::routing::delete },
        "PATCH" => quote! { axum::routing::patch },
        _ => quote! { axum::routing::get },
    };

    let auth_middleware = if auth_required {
        quote! {
            .layer(axum::middleware::from_fn(auth_middleware))
        }
    } else {
        quote! {}
    };

    let route_lit = syn::LitStr::new(&route, name.span());

    let generics = &input.generics;
    let phantom_generics: Vec<_> = generics.params.iter().map(|param| {
        match param {
            GenericParam::Type(t) => {
                let ident = &t.ident;
                quote! { std::marker::PhantomData<#ident> }
            }
            GenericParam::Lifetime(l) => {
                let lifetime = &l.lifetime;
                quote! { std::marker::PhantomData<&#lifetime ()> }
            }
            GenericParam::Const(c) => {
                let ident = &c.ident;
                quote! { std::marker::PhantomData<[(); #ident]> }
            }
        }
    }).collect();

    let expanded = quote! {
        #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
        pub struct #request_type {
            #(#request_fields,)*
        }

        #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
        pub struct #response_type {
            #(#response_fields,)*
            #(pub #phantom_generics,)*
        }

        pub async fn #handler_name(
            axum::Json(req): axum::Json<#request_type>,
        ) -> Result<axum::Json<#response_type>, AppError> {
            let response = #name::handle(req).await?;
            Ok(axum::Json(response))
        }

        impl #name {
            pub fn register(router: axum::Router<SharedState>) -> axum::Router<SharedState> {
                router.route(#route_lit, #method_ident(#handler_name))
                    #auth_middleware
            }
        }
    };

    expanded.into()
}

fn get_string_attr(attrs: &[Attribute], name: &str) -> Option<String> {
    attrs.iter().find_map(|attr| {
        if attr.path.is_ident(name) {
            if let Ok(Meta::List(list)) = attr.parse_meta() {
                if let Some(NestedMeta::Lit(Lit::Str(s))) = list.nested.first() {
                    return Some(s.value());
                }
            }
        }
        None
    })
}

fn get_bool_attr(attrs: &[Attribute], name: &str) -> Option<bool> {
    attrs.iter().find_map(|attr| {
        if attr.path.is_ident(name) {
            if let Ok(Meta::Path(_)) = attr.parse_meta() {
                return Some(true);
            }
        }
        None
    })
}

fn has_attr(attrs: &[Attribute], name: &str) -> bool {
    attrs.iter().any(|attr| attr.path.is_ident(name))
}

fn to_snake_case(s: &str) -> String {
    let mut result = String::new();
    for (i, c) in s.chars().enumerate() {
        if c.is_uppercase() && i > 0 {
            result.push('_');
        }
        result.push(c.to_lowercase().next().unwrap());
    }
    result
}
use my_derive::ApiEndpoint;

#[derive(ApiEndpoint)]
#[api_route("/api/users")]
#[api_method("GET")]
#[api_auth]
pub struct ListUsers {
    pub page: u32,
    pub page_size: u32,
    #[request_only]
    pub search: Option<String>,
    #[response_only]
    pub users: Vec<User>,
    #[response_only]
    pub total: u64,
}

#[derive(ApiEndpoint)]
#[api_route("/api/users")]
#[api_method("POST")]
pub struct CreateUser {
    pub name: String,
    pub email: String,
    #[response_only]
    pub id: String,
    #[response_only]
    pub created_at: String,
}

避坑指南

坑1:macro_rules!的hygiene陷阱

// ❌ 錯誤:宣告巨集中引入的變數名可能與呼叫處衝突
macro_rules! swap {
    ($a:expr, $b:expr) => {
        let temp = $a;  // temp可能與呼叫處的temp衝突
        $a = $b;
        $b = temp;
    };
}

// ✅ 正確:使用唯一前綴避免命名衝突
macro_rules! swap {
    ($a:expr, $b:expr) => {
        let __swap_temp = $a;
        $a = $b;
        $b = __swap_temp;
    };
}

坑2:過程巨集crate放在主crate中

# ❌ 錯誤:過程巨集和主程式碼放在同一個crate
[package]
name = "my_app"
[lib]
proc-macro = true  # 編譯報錯!proc-macro crate不能有非巨集程式碼

# ✅ 正確:過程巨集放在獨立crate
# my_derive/Cargo.toml
[package]
name = "my_derive"
[lib]
proc-macro = true

# my_app/Cargo.toml
[dependencies]
my_derive = { path = "../my_derive" }

坑3:TokenStream偵錯看不到輸出

// ❌ 錯誤:過程巨集中println!不會在編譯時輸出
#[proc_macro_derive(MyDebug)]
pub fn derive_debug(input: TokenStream) -> TokenStream {
    println!("Debugging: {}", input);  // 編譯時看不到!
    // ...
}

// ✅ 正確:使用eprintln!輸出到stderr,或用cargo expand檢視
#[proc_macro_derive(MyDebug)]
pub fn derive_debug(input: TokenStream) -> TokenStream {
    eprintln!("Debugging: {}", input);  // 編譯時輸出到stderr
    // ...
}
// 然後執行: RUST_BACKTRACE=1 cargo build 2>&1 | head
// 或: cargo expand

坑4:syn解析泛型時遺漏where子句

// ❌ 錯誤:忽略泛型約束,生成的程式碼可能不編譯
let name = &input.ident;
let generics = &input.generics;
quote! {
    impl #generics MyTrait for #name #generics { ... }
}

// ✅ 正確:使用split_for_impl正確處理泛型
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
quote! {
    impl #impl_generics MyTrait for #name #ty_generics #where_clause { ... }
}

坑5:屬性巨集消耗原始item

// ❌ 錯誤:屬性巨集沒有保留原始函式,導致其他屬性失效
#[proc_macro_attribute]
pub fn my_attr(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let input = parse_macro_input!(item as ItemFn);
    // 只回傳新程式碼,原始ItemFn被丟棄
    quote! { fn new_func() {} }.into()
}

// ✅ 正確:在原始item基礎上包裝,保留原始程式碼
#[proc_macro_attribute]
pub fn my_attr(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let input = parse_macro_input!(item as ItemFn);
    let ItemFn { attrs, vis, sig, block, .. } = input;
    quote! {
        #(#attrs)*
        #vis #sig {
            // 前置邏輯
            let __result = #block;
            // 後置邏輯
            __result
        }
    }.into()
}

報錯排查

序號 報錯資訊 原因 解決方法
1 proc-macro crate cannot export non-macro items proc-macro crate中定義了非巨集的pub項 將輔助型別/函式移到獨立crate,proc-macro crate只放巨集定義
2 cannot find macro my_derive in this scope 未新增derive crate依賴 在Cargo.toml中新增my_derive = { path = "..." }
3 expected ident, found punctuation syn解析TokenStream時期望識別符號但遇到符號 檢查巨集呼叫語法,確保引數順序和型別匹配
4 recursion limit reached while expanding macro 宣告巨集遞迴展開超過預設限制 在lib.rs頂部新增#![recursion_limit = "256"]
5 mismatched types: expected TokenStream, found String 過程巨集回傳String而非TokenStream 使用quote!{...}.into()input.to_string().parse().unwrap()
6 the trait bound X: Y is not satisfied 巨集生成的程式碼缺少trait約束 在泛型引數上新增約束:where T: Trait
7 procedural macro panicked 過程巨集內部panic 使用syn::Error::new_spanned()回傳編譯錯誤而非panic
8 variable 'temp' is not available in current scope 宣告巨集hygiene問題,引入的變數不可見 使用$crate::前綴或將變數作為引數傳入
9 cargo expand shows unexpected code 巨集展開結果與預期不符 在過程巨集中新增eprintln!偵錯,逐步檢查quote!輸出
10 attribute macro must be in proc-macro crate 屬性巨集定義在普通crate中 將屬性巨集移到proc-macro = true的獨立crate

進階最佳化

1. 過程巨集錯誤報告與Span定位

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput, Data, Fields, Error};

#[proc_macro_derive(Validate)]
pub fn derive_validate(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;

    let validations = match &input.data {
        Data::Struct(data) => {
            match &data.fields {
                Fields::Named(fields) => {
                    let field_checks: Vec<_> = fields.named.iter().map(|f| {
                        let field_name = f.ident.as_ref().unwrap();
                        let field_name_str = field_name.to_string();

                        let type_checks = if let Some(attr) = f.attrs.iter().find(|a| a.path.is_ident("validate")) {
                            let mut checks = Vec::new();
                            if let Ok(meta) = attr.parse_meta() {
                                if let syn::Meta::List(list) = meta {
                                    for nested in list.nested.iter() {
                                        match nested {
                                            syn::NestedMeta::Meta(syn::Meta::Path(path)) => {
                                                if path.is_ident("non_empty") {
                                                    checks.push(quote! {
                                                        if self.#field_name.is_empty() {
                                                            return Err(format!("Field '{}' must not be empty", #field_name_str));
                                                        }
                                                    });
                                                } else if path.is_ident("positive") {
                                                    checks.push(quote! {
                                                        if self.#field_name <= 0 {
                                                            return Err(format!("Field '{}' must be positive", #field_name_str));
                                                        }
                                                    });
                                                }
                                            }
                                            _ => {}
                                        }
                                    }
                                }
                            }
                            checks
                        } else {
                            vec![]
                        };

                        Ok(type_checks)
                    }).collect::<Result<Vec<_>, _>>();

                    match field_checks {
                        Ok(checks) => checks.into_iter().flatten().collect(),
                        Err(e) => return e.to_compile_error().into(),
                    }
                }
                _ => {
                    return Error::new_spanned(
                        name,
                        "Validate derive only supports structs with named fields",
                    ).to_compile_error().into();
                }
            }
        }
        _ => {
            return Error::new_spanned(
                name,
                "Validate derive only supports structs",
            ).to_compile_error().into();
        }
    };

    let expanded = quote! {
        impl #name {
            pub fn validate(&self) -> Result<(), String> {
                #(#validations)*
                Ok(())
            }
        }
    };

    expanded.into()
}

2. 宣告巨集的遞迴與TT咀嚼器

macro_rules! impl_from_for_enum {
    (@inner $name:ident { $($variant:ident($inner:ty)),* $(,)? }) => {
        $(
            impl From<$inner> for $name {
                fn from(value: $inner) -> Self {
                    $name::$variant(value)
                }
            }
        )*
    };

    ($name:ident { $($variant:ident($inner:ty)),* $(,)?) => {
        impl_from_for_enum!(@inner $name { $($variant($inner)),* });
    };
}

impl_from_for_enum! {
    AppError {
        Io(std::io::Error),
        Serde(serde_json::Error),
        Http(reqwest::Error),
        Database(sqlx::Error),
    }
}

macro_rules! hash_map {
    (@single $($x:tt)*) => { () };
    (@count $($rest:expr),*) => { <[()]>::len(&[$(hash_map!(@single $rest)),*]) };

    ($($key:expr => $value:expr),* $(,)?) => {{
        let cap = hash_map!(@count $($key),*);
        let mut map = std::collections::HashMap::with_capacity(cap);
        $(
            map.insert($key, $value);
        )*
        map
    }};
}

let config = hash_map! {
    "host" => "localhost",
    "port" => "8080",
    "database" => "myapp",
};

3. 過程巨集的增量編譯與效能最佳化

use proc_macro::TokenStream;
use quote::quote;
use syn::parse_macro_input;

#[proc_macro_derive(LargeDerive, attributes(large_attr))]
pub fn derive_large(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as syn::DeriveInput);

    let name = &input.ident;
    let generics = &input.generics;
    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();

    let trait_impl = generate_trait_impl(&input);

    quote! {
        impl #impl_generics LargeTrait for #name #ty_generics #where_clause {
            #trait_impl
        }
    }.into()
}

fn generate_trait_impl(input: &syn::DeriveInput) -> proc_macro2::TokenStream {
    let name = &input.ident;

    match &input.data {
        syn::Data::Struct(data) => {
            match &data.fields {
                syn::Fields::Named(fields) => {
                    let field_count = fields.named.len();
                    if field_count > 50 {
                        return syn::Error::new_spanned(
                            name,
                            format!("Struct has {} fields, consider splitting into smaller structs", field_count),
                        ).to_compile_error();
                    }

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

                    let field_strs: Vec<String> = field_names.iter().map(|f| f.to_string()).collect();

                    quote! {
                        fn field_count() -> usize {
                            #field_count
                        }

                        fn field_names() -> Vec<&'static str> {
                            vec![#(#field_strs),*]
                        }
                    }
                }
                _ => quote! { fn field_count() -> usize { 0 } },
            }
        }
        _ => quote! { fn field_count() -> usize { 0 } },
    }
}

對比分析

維度 macro_rules! Derive巨集 屬性巨集 函式式過程巨集 C++模板
定義方式 macro_rules! #[proc_macro_derive] #[proc_macro_attribute] #[proc_macro] template<>
輸入型別 模式匹配 TokenStream TokenStream×2 TokenStream 型別引數
輸出型別 模板替換 TokenStream TokenStream TokenStream 型別/函式
AST存取 ❌無 ✅syn解析 ✅syn解析 ✅syn解析 ❌無
錯誤報告 ⚠️差 ✅Span定位 ✅Span定位 ✅Span定位 ❌極差
偵錯方式 cargo expand cargo expand cargo expand cargo expand 編譯錯誤
學習曲線 ⭐中 ⭐陡 ⭐陡 ⭐陡 ⭐極陡
適用場景 DSL/模板 自動實作trait 裝飾器/程式碼注入 自訂語法 泛型演算法
獨立crate ❌不需要 ✅必須 ✅必須 ✅必須 ❌不需要
編譯速度 ⭐快 ⭐慢 ⭐慢 ⭐慢 ⭐極慢
型別安全 ❌無 ⚠️展開後檢查 ⚠️展開後檢查 ⚠️展開後檢查 ✅編譯時

總結:Rust巨集元程式設計的核心優勢在於編譯時程式碼生成——從宣告巨集的模板替換到過程巨集的AST變換,巨集讓你在不犧牲執行時效能的前提下消除重複程式碼。2026年的生產實踐:用macro_rules!構建DSL和模板→Derive巨集自動實作trait→Attribute巨集做裝飾器→Function-like過程巨集自訂語法→syn+quote處理複雜程式碼生成。關鍵是要理解巨集的邊界——巨集是程式碼生成器,不是執行時抽象;能用泛型解決的不要用巨集,能用trait解決的不要用巨集,巨集是最後的武器。


線上工具推薦

本站提供瀏覽器本地工具,免註冊即可試用 →

#Rust宏#声明宏#过程宏#元编程#代码生成#2026#编程语言