【问题标题】:How do I generate `quote::Tokens` from both a constant value and a collection of values?如何从常量值和值集合生成 `quote::Tokens`?
【发布时间】:2017-09-01 14:50:06
【问题描述】:

我正在创建一个在枚举上运行的custom derive。我想生成类似的代码

match *enum_instance {
    EnumName::VariantName1 => "dummy",
    EnumName::VariantName2 => "dummy",
    EnumName::VariantName3 => "dummy",
}

我已经能够使用这样的代码让它工作:

let enum_name = &ast.ident;
let mut q = quote! {};

q.append_all(e.iter().map(|variant| {
    let variant_name = &variant.ident;
    quote! { #enum_name::#variant_name => "dummy", }
}));

quote! {
    impl FullName for #name {
        fn full_name(&self) -> &'static str {
            match *self {
                #q
            }
        }
    }
}

需要临时变量并执行append_all 感觉非常不雅。有更清洁的解决方案吗?

构建 MCVE 的代码

src/main.rs

#[macro_use]
extern crate my_derive;

#[derive(FullName)]
enum Example {
    One,
    Two,
    Three,
}

trait FullName {
    fn full_name(&self) -> &'static str;
}

fn main() {
    assert_eq!(Example::One.full_name(), "dummy");
}

Cargo.toml

[package]
name = "example"
version = "0.0.0"

[dependencies]
my-derive = { path = "my-derive" }

my-derive/Cargo.toml

[package]
name = "my-derive"
version = "0.0.0"

[lib]
proc-macro = true

[dependencies]
quote = "0.3.12"
syn = "0.11.10"

my-derive/src/lib.rs

extern crate proc_macro;
extern crate syn;
#[macro_use]
extern crate quote;

use proc_macro::TokenStream;

#[proc_macro_derive(FullName)]
pub fn has_extent_derive(input: TokenStream) -> TokenStream {
    let s = input.to_string();
    let ast = syn::parse_macro_input(&s).expect("Unable to parse input");
    let gen = impl_full_name(&ast);
    gen.parse().expect("Unable to generate")
}

fn impl_full_name(ast: &syn::MacroInput) -> quote::Tokens {
    use syn::Body;

    let name = &ast.ident;

    match ast.body {
        Body::Enum(ref e) => {
            let enum_name = &ast.ident;
            let mut q = quote! {};

            q.append_all(e.iter().map(|variant| {
                let variant_name = &variant.ident;
                quote! { #enum_name::#variant_name => "dummy", }
            }));

            quote! {
                impl FullName for #name {
                    fn full_name(&self) -> &'static str {
                        match *self {
                            #q
                        }
                    }
                }
            }
        }
        _ => {
            panic!("Only implemented for enums");
        }
    }
}

【问题讨论】:

    标签: macros rust


    【解决方案1】:

    当您有一个标记 xs = [x1, x2, …, xN] 的迭代器时,您可以使用 repetition 语法 #( #xs & stuff );* 将其扩展为 x1 & stuff; x2 & stuff; …; xN & stuff 内的 quote! 宏。

    同样,您可以并行重复多个迭代器,例如#(#ks => #vs,)* 将变为 k1 => v1, k2 => v2, …, kN => vN,

    quote的重复语法与Rust's own macro system类似,只是将$改为#。理想情况下,您应该能够编写:

    Body::Enum(ref e) => {
        let enum_name = &ast.ident;
        let variant_names = e.iter().map(|variant| &variant.ident);
    
        quote! {
            impl FullName for #name {
                fn full_name(&self) -> &'static str {
                    match *self {
                        #(#enum_name::#variant_names => "dummy",)*
    //                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                    }
                }
            }
        }
    }
    

    然而,目前quote 要求#(…)* 中的每个变量都是一个迭代器:编译上面会导致 E0599 method-not-found 错误。 This is a known bug of the quote crate。正如错误报告所解释的,这可以通过使用std::iter::repeat() 创建一个永远重复enum_name 的迭代器来解决:

    Body::Enum(ref e) => {
        use std::iter::repeat;
    
        let enum_names = repeat(&ast.ident);
        //               ^~~~~~~~~~~~~~~~~~ 
        //                creates the iterator [Example, Example, Example, Example, …]
        let variant_names = e.iter().map(|variant| &variant.ident);
    
        quote! {
            impl FullName for #name {
                fn full_name(&self) -> &'static str {
                    match *self {
                        #(#enum_names::#variant_names => "dummy",)*
                    }
                }
            }
        }
    }
    

    这会产生

    impl FullName for Example {
        fn full_name(&self) -> &'static str {
            match *self {
                Example::One => "dummy",
                Example::Two => "dummy",
                Example::Three => "dummy",
            }
        }
    }
    

    在最终输出中。

    【讨论】:

      猜你喜欢
      • 2012-12-01
      • 1970-01-01
      • 2023-02-07
      • 2011-11-17
      • 2016-09-03
      • 2014-02-01
      • 2021-06-12
      • 2017-01-02
      • 1970-01-01
      相关资源
      最近更新 更多