问题分析

#[derive(CustomDebug)]
pub struct Field {
    name: &'static str,
    #[debug = "0b{:08b}"]
    bitmask: u8,
}

你遇到了两个错误:

  1. Name-value arguments are not supported. Use #[debug(...)] - 这表明 Rust 无法识别 #[debug = "value"] 格式,而期望 #[debug(...)] 格式。
  2. cannot find attribute debug in this scope - 这表明 debug 属性没有被正确注册。

解决方案

在 Rust 的过程宏系统中,自定义属性需要特殊处理。对于派生宏上的自定义属性,我们需要使用特定的语法。让我们修改代码:

如何使用 darling 实现 CustomDebug 派生宏

你遇到的问题涉及 Rust 宏系统中对属性语法的处理方式。我已经提供了修复后的解决方案,下面我会详细解释问题的原因和解决方法。

问题根源

在 Rust 中,自定义属性有特定的语法要求:

  1. 原来的写法 #[debug = "0b{:08b}"] 在 Rust-darling lib 处理方式下不是有效的自定义属性语法。
  2. 正确的语法应该是 #[debug(format = "0b{:08b}")],即使用圆括号而非等号。

完整解决方案

1. 修改属性解析器

首先,我们需要修改 DebugField 结构体来正确解析属性:

#[derive(FromField)]
#[darling(attributes(debug))]
struct DebugField {
    ident: Option<syn::Ident>,
    ty: syn::Type,
    #[darling(default)]
    format: Option<String>, // 改为接收 #[debug(format = "...")] 中的格式字符串
}

这里关键的改变是将 debug_format 改为 format,这样 darling 会尝试解析 #[debug(format = "...")] 格式的属性。

2. 正确注册属性

在派生宏的定义中,我们需要声明它使用了 debug 属性:

#[proc_macro_derive(CustomDebug, attributes(debug))]

attributes(debug) 告诉 Rust 编译器这个派生宏会处理名为 debug 的属性,这样就不会报"找不到属性"的错误。

3. 生成格式化代码

在生成代码时,我们需要使用字段的 format 属性(而非之前的 debug_format):

match &field.format {
    Some(format_str) => {
        quote! {
            field_parts.push(format!("{}: {}", #field_name_str, 
                format!(#format_str, self.#field_name)));
        }
    },
    None => {
        quote! {
            field_parts.push(format!("{}: {:?}", #field_name_str, self.#field_name));
        }
    }
}

最终代码:

use darling::{FromDeriveInput, FromField};
use proc_macro::TokenStream;
use quote::quote;
use syn::parse_macro_input;

/// 用于解析字段上的 #[debug(format = "...")] 属性
#[derive(FromField)]
#[darling(attributes(debug))]
struct DebugField {
    ident: Option<syn::Ident>,
    ty: syn::Type,
    #[darling(default)]
    format: Option<String>, // 接收 #[debug(format = "...")] 中的格式字符串
}

/// 用于解析整个结构体
#[derive(FromDeriveInput)]
#[darling(supports(struct_named, struct_tuple), attributes(debug))]
struct CustomDebugReceiver {
    ident: syn::Ident,
    generics: syn::Generics,
    data: darling::ast::Data<(), DebugField>,
}

#[proc_macro_derive(CustomDebug, attributes(debug))]
pub fn derive_custom_debug(input: TokenStream) -> TokenStream {
    // 解析输入的 TokenStream
    let input = parse_macro_input!(input as syn::DeriveInput);
    
    // 使用 darling 解析结构体及其字段
    let receiver = match CustomDebugReceiver::from_derive_input(&input) {
        Ok(receiver) => receiver,
        Err(err) => return err.write_errors().into(),
    };
    
    // 生成 Debug 实现
    let name = &receiver.ident;
    let (impl_generics, ty_generics, where_clause) = receiver.generics.split_for_impl();
    
    // 处理字段
    let debug_fields = match &receiver.data {
        darling::ast::Data::Struct(fields) => {
            let fields = fields.iter().enumerate().map(|(i, field)| {
                let field_name = match &field.ident {
                    Some(ident) => quote! { #ident },
                    None => {
                        let index = syn::Index::from(i);
                        quote! { #index }
                    }
                };
                
                let field_name_str = match &field.ident {
                    Some(ident) => ident.to_string(),
                    None => i.to_string(),
                };
                
                // 根据是否有自定义的格式化字符串生成不同的格式化代码
                match &field.format {
                    Some(format_str) => {
                        quote! {
                            field_parts.push(format!("{}: {}", #field_name_str, 
                                format!(#format_str, self.#field_name)));
                        }
                    },
                    None => {
                        quote! {
                            field_parts.push(format!("{}: {:?}", #field_name_str, self.#field_name));
                        }
                    }
                }
            });
            
            quote! { #(#fields)* }
        },
        _ => panic!("CustomDebug 只支持结构体"),
    };
    
    // 生成最终的实现代码
    let expanded = quote! {
        impl #impl_generics std::fmt::Debug for #name #ty_generics #where_clause {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                let mut field_parts = Vec::new();
                #debug_fields
                write!(f, "{} {{ {} }}", stringify!(#name), field_parts.join(", "))
            }
        }
    };
    
    expanded.into()
}

正确的使用方式

有了这些修改后,你可以这样使用 CustomDebug 派生宏:

// 使用自定义的 CustomDebug 派生宏
#[derive(CustomDebug)]
pub struct Field {
    name: &'static str,
    #[debug(format = "0b{:08b}")]  // 注意这里的语法变化
    bitmask: u8,
    #[debug(format = "{:.2f}°C")]   // 另一个格式化示例
    temperature: f32,
}

fn main() {
    let field = Field {
        name: "控制标志",
        bitmask: 10,
        temperature: 36.75,
    };
    
    println!("{:?}", field);
    // 输出:Field { name: "控制标志", bitmask: 0b00001010, temperature: 36.75°C }
}

当你打印这个结构体时,输出将类似于:

Field { name: "控制标志", bitmask: 0b00001010 }

深入解释:Rust 属性系统

Rust 的属性系统基于以下语法规则:

  1. 外部属性#[attribute]
  2. 内部属性#![attribute]

属性可以有多种形式:

  • 单一标识符:#[derive]
  • 标识符=字面量:#[path = "foo.rs"]
  • 标识符 (...):#[derive(Debug)]
  • 标识符 (key = value):#[cfg(target_os = "windows")]

在过程宏中,当我们想要定义自己的属性时,必须遵循这些规则。darling 库帮助我们解析这些属性,但我们需要正确指定要解析的格式。

工作原理图解

用户代码                 派生宏处理                     生成代码
+----------------+     +------------------+     +------------------+
| #[derive(      |     | 1. 解析结构体和属性 |     | impl Debug for  |
|   CustomDebug  |---->| 2. 提取格式字符串  |---->| Field {         |
| )]             |     | 3. 生成格式化代码  |     |   fmt(...) {...}|
| struct Field { |     |                  |     | }                |
|   #[debug(...)]|     |                  |     |                  |
|   field: Type  |     |                  |     |                  |
| }              |     |                  |     |                  |
+----------------+     +------------------+     +------------------+

扩展思路

  1. 支持更多格式化选项:你可以扩展属性语法,例如添加对齐、填充等选项:

    #[debug(format = "0b{:08b}", align = "right", width = "10")]
    
  2. 支持条件格式化:可以添加条件来决定是否格式化某个字段:

    #[debug(format = "0b{:08b}", skip_if_zero = "true")]
    
  3. 支持枚举类型:当前实现只支持结构体,你可以扩展它来支持枚举。

  4. 自定义结构体级别的格式化:可以添加结构体级别的属性来自定义整个结构体的格式化方式。

总结

通过正确理解 Rust 的属性语法并使用 darling 库,我们成功实现了一个自定义的 CustomDebug 派生宏,它允许为每个字段指定不同的格式化字符串。关键的修复点是:

  1. 将属性语法从 #[debug = "..."] 更改为 #[debug(format = "...")]
  2. 在派生宏中正确注册 debug 属性
  3. DebugField 结构体中使用正确的字段名来接收格式字符串

这种方法使得我们可以灵活地为任何字段自定义 Debug 输出格式,提高了调试信息的可读性和实用性。

附录

同时支持 #[debug = "..."]#[debug(format = "...")] 这两种语法

use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::spanned::Spanned;
use syn::{parse_macro_input, Data, DeriveInput, Field, Fields};

#[proc_macro_derive(CustomDebug, attributes(debug))]
pub fn derive(input: TokenStream) -> TokenStream {
    // 解析输入的 TokenStream 为 DeriveInput
    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_fields = match &input.data {
        Data::Struct(data) => match &data.fields {
            Fields::Named(fields) => {
                let field_debugs = fields
                    .named
                    .iter()
                    .enumerate()
                    .map(|(i, field)| generate_field_debug(field, i, true));
                quote! { #(#field_debugs)* }
            }
            Fields::Unnamed(fields) => {
                let field_debugs = fields
                    .unnamed
                    .iter()
                    .enumerate()
                    .map(|(i, field)| generate_field_debug(field, i, false));
                quote! { #(#field_debugs)* }
            }
            Fields::Unit => quote! {},
        },
        _ => {
            return syn::Error::new(input.span(), "CustomDebug 只支持结构体")
                .to_compile_error()
                .into();
        }
    };

    // 生成最终的实现代码
    let expanded = quote! {
        impl #impl_generics std::fmt::Debug for #name #ty_generics #where_clause {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                let mut field_parts = Vec::new();
                #debug_fields
                write!(f, "{} {{ {} }}", stringify!(#name), field_parts.join(", "))
            }
        }
    };

    expanded.into()
}

// 为字段生成调试代码
fn generate_field_debug(field: &Field, index: usize, is_named: bool) -> TokenStream2 {
    let field_name = if is_named {
        field.ident.as_ref().unwrap().to_string()
    } else {
        index.to_string()
    };

    let field_access = if is_named {
        let ident = field.ident.as_ref().unwrap();
        quote! { self.#ident }
    } else {
        let index = syn::Index::from(index);
        quote! { self.#index }
    };

    // 查找 debug 属性并提取格式字符串
    let format_str = find_debug_format(field);

    match format_str {
        Some(format) => {
            quote! {
                field_parts.push(format!("{}: {}", #field_name,
                    format!(#format, #field_access)));
            }
        }
        None => {
            quote! {
                field_parts.push(format!("{}: {:?}", #field_name, #field_access));
            }
        }
    }
}

// 从字段属性中查找 debug 格式字符串
fn find_debug_format(field: &Field) -> Option<String> {
    for attr in &field.attrs {
        if attr.path().is_ident("debug") {
            // 解析 #[debug = "..."] 形式
            if let Ok(value) = attr.meta.require_name_value() {
                if let syn::Expr::Lit(expr_lit) = &value.value {
                    if let syn::Lit::Str(lit_str) = &expr_lit.lit {
                        return Some(lit_str.value());
                    }
                }
                continue;
            }

            // 解析 #[debug(format = "...")] 形式
            if let Ok(meta) = attr.meta.require_list() {
                let mut result = None;
                let _ = meta.parse_nested_meta(|nested| {
                    if nested.path.is_ident("format") {
                        let value = nested.value()?;
                        let lit: syn::LitStr = value.parse()?;
                        result = Some(lit.value());
                    }
                    Ok(())
                });
                if result.is_some() {
                    return result;
                }
            }
        }
    }
    None
}