rust 如何在过程宏中显式地要求一个Trait用于函数的所有参数?

kqhtkvqz  于 2023-08-05  发布在  其他
关注(0)|答案(2)|浏览(126)

短版

简短的版本是:当编写一个过程宏wrap来 Package 一个函数时(有点像trace crate的方式),要求所有参数实现一个特定的trait的最佳方法是什么?

问题是

trace扩展到对println!("{:?}", i)的调用,因此:

struct MyType(u32);

#[trace]
fn foo(i: MyType) {
    println!("MyType: {}", i.0);
}

字符串
我明白

error[E0277]: `MyType` doesn't implement `Debug`
  --> src/main.rs:10:8
   |
9  | #[trace]
   | ------- in this procedural macro expansion
10 | fn foo(i: MyType) {
   |        ^ `MyType` cannot be formatted using `{:?}`
   |
   = help: the trait `Debug` is not implemented for `MyType`


这是很好的和明确的:它显示了参数声明中的错误,并指出MyType应该实现Debug。
现在我尝试做同样的事情,但扩展到类似MyTrait::my_trait_method(&i)的东西,仍然得到基本相同的错误消息。

我所尝试的

到目前为止,我所能做的最好的就是在通过quote!宏生成的扩展中添加Where子句(我仍然在弄清楚如何使用syn crate,所以它仍然是硬编码的),至少我确实得到了一条明确的消息,说MyType没有实现MyTrait,但错误是在宏上报告的,而不是在参数定义上:

#[proc_macro_attribute]
pub fn wrap(_macro_args: TokenStream, input: TokenStream) -> TokenStream {
    let func = parse_macro_input!(input as syn::ItemFn);
    let orig_name = func.sig.ident.to_string();
    let params: Punctuated<Pat, Token![,]> = func
        .sig
        .inputs
        .iter()
        .map(|input| match input {
            FnArg::Typed(PatType { pat: t, .. }) => t.as_ref().clone(),
            _ => panic!("Unexpected input for function {orig_name}: {input:?}"),
        })
        .collect();

    let pattern = "{:?}, ".repeat(params.len());
    quote! {
        fn foo(i: MyType) where MyType: MyTrait{
            //i.my_trait_method();
            println!(#pattern, #params);
        }
    }
    .into()
}

x

#[trace]
fn foo(i: MyType) {
    println!("MyType: {}", i.0);
}
error[E0277]: the trait bound `MyType: MyTrait` is not satisfied
  --> src/main.rs:11:1
   |
11 | #[wrap]
   | ^^^^^^^ the trait `MyTrait` is not implemented for `MyType`

的一种或多种
是生成Where子句的方法吗(如果是这样的话,我会正确地做),或者有更好的方法来要求在proc_macro中解析的函数的所有参数都有特定的trait?

xjreopfe

xjreopfe1#

虽然Chayim Friedman的答案是正确的,但我想补充的是,您仍然可以使用标识符和span来改善错误。
在下面的代码中,您创建了一个标识符并指向参数的“span”(基本上是代码中这些参数的原始位置),而不是硬编码Trait。代码的其余部分几乎没有变化。

// ...
use syn::Ident;

#[proc_macro_attribute]
pub fn wrap(_macro_args: TokenStream, input: TokenStream) -> TokenStream {
    let func = parse_macro_input!(input as syn::ItemFn);
    let orig_name = func.sig.ident.to_string();
    let params: Punctuated<Pat, Token![,]> = func
        .sig
        .inputs
        .iter()
        .map(|input| match input {
            FnArg::Typed(PatType { pat: t, .. }) => t.as_ref().clone(),
            _ => panic!("Unexpected input for function {orig_name}: {input:?}"),
        })
        .collect();

    let trait_ident = Ident::new("MyTrait", params.span());

    let pattern = "{:?}, ".repeat(params.len());
    quote! {
        fn foo(i: MyType) where MyType: #trait_ident {
            println!(#pattern, #params);
        }
    }
    .into()
}

字符串
现在,使用以下代码,也是基于您的示例:

#[derive(Debug)]
struct MyType(u32);

trait MyTrait {}

// activate if you do not want an error
// impl MyTrait for MyType {}

#[wrap]
fn foo(i: MyType) {
    println!("MyType: {}", i.0);
}

fn main() {}


你会得到这个错误:

error[E0277]: the trait bound `MyType: MyTrait` is not satisfied
  --> src/main.rs:12:8
   |
12 | fn foo(i: MyType) {
   |        ^ the trait `MyTrait` is not implemented for `MyType`
   |
   = help: see issue #48214


对我来说更有帮助。

vyu0f0g1

vyu0f0g12#

不幸的是,你不能这么做。
Debug是特殊的,因为它在标准库中,标准库可以使用不稳定的内部特性。它使用#[rustc_on_unimplemented]来指定自定义错误消息。
它看起来像这样:

#[rustc_on_unimplemented(
    on(
        crate_local,
        label = "`{Self}` cannot be formatted using `{{:?}}`",
        note = "add `#[derive(Debug)]` to `{Self}` or manually `impl {Debug} for {Self}`"
    ),
    message = "`{Self}` doesn't implement `{Debug}`",
    label = "`{Self}` cannot be formatted using `{{:?}}` because it doesn't implement `{Debug}`"
)]

字符串
你可以为你的特质做一些类似的事情,但这需要你每晚坚持,这是一个没有文档的内部特性。我不建议你那样做。
There is an RFC to allow you to use that in your code,但目前尚未实现。

相关问题