third_party_rust_quote:基于 Rust 生态的准引用库项目

提供用于生成Rust代码的宏。 | A Rust library that provides support for generating Rust code.

分支231Tags30
文件最后提交记录最后更新时间
4 个月前
4 个月前
4 个月前
4 个月前
4 个月前
4 个月前
4 个月前
2 年前
3 年前
3 个月前
4 个月前
4 个月前
3 个月前
4 年前

Rust 准引用

github crates.io docs.rs build status

本 crate 提供了 quote! 宏,用于将 Rust 语法树数据结构转换为源代码令牌。

Rust 中的过程宏接收令牌流作为输入,执行任意 Rust 代码以确定如何处理这些令牌,并生成令牌流返回给编译器,以便编译到调用者的 crate 中。准引用就是解决其中一个环节的方案——生成返回给编译器的令牌。

准引用的理念是,我们编写的代码将被视为数据。在 quote! 宏内部,我们可以编写在文本编辑器或 IDE 中看起来像代码的内容。我们能充分利用编辑器的括号匹配、语法高亮、缩进,甚至可能还有自动补全功能。但这段内容不会作为代码编译到当前 crate 中,而是被当作数据,可以传递、修改,并最终作为令牌返回给编译器,编译到宏调用者的 crate 里。

本 crate 的开发初衷是服务于过程宏的使用场景,但它同时也是一个通用的 Rust 准引用库,并非专门针对过程宏。

[dependencies]
quote = "1.0"

版本要求:Quote 支持 rustc 1.68 及更高版本。
发布说明


语法

quote crate 提供了一个 quote! 宏,您可以在其中编写 Rust 代码,这些代码会被打包成一个 TokenStream,并可被视为数据。您可以将 TokenStream 理解为表示一段 Rust 源代码片段。

quote! 宏中,使用 #var 进行插值。任何实现了 quote::ToTokens trait 的类型都可以进行插值。这包括大多数 Rust 基本类型以及来自 syn 的大多数语法树类型。

let tokens = quote! {
    struct SerializeWith #generics #where_clause {
        value: &'a #field_ty,
        phantom: core::marker::PhantomData<#item_ty>,
    }

    impl #generics serde::Serialize for SerializeWith #generics #where_clause {
        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
        where
            S: serde::Serializer,
        {
            #path(self.value, serializer)
        }
    }

    SerializeWith {
        value: #value,
        phantom: core::marker::PhantomData::<#item_ty>,
    }
};

重复

重复功能通过 #(...)*#(...),* 实现,类似于 macro_rules!。它会遍历重复块内任何插值变量的元素,并为每个元素插入一份重复体的副本。插值中的变量可以是 Vec、切片、BTreeSet 或任何 Iterator

  • #(#var)* — 无分隔符
  • #(#var),* — 星号前的字符用作分隔符
  • #( struct #var; )* — 重复块中可包含其他内容
  • #( #k => println!("{}", #v), )* — 甚至可以包含多个插值

请注意,#(#var ,)*#(#var),* 之间存在区别——后者不会生成尾随逗号。这与 macro_rules! 中分隔符的行为一致。


向编译器返回令牌

quote! 宏计算后得到的表达式类型为 proc_macro2::TokenStream。而 Rust 过程宏期望返回 proc_macro::TokenStream 类型。

这两种类型的区别在于,proc_macro 类型完全特定于过程宏,绝不可能存在于过程宏之外的代码中,而 proc_macro2 类型可以存在于任何地方,包括测试以及 main.rs 和 build.rs 等非宏代码中。这就是为什么即使是过程宏生态系统也主要围绕 proc_macro2 构建,因为这样可以确保库是可单元测试的,并且能在非宏上下文中使用。

这两种类型之间存在双向的 From 转换,因此从过程宏返回 quote! 的输出通常写作 tokens.into()proc_macro::TokenStream::from(tokens)


示例

组合引用片段

通常,您不会一次性构建整个最终的 TokenStream。不同的部分可能来自不同的辅助函数。quote! 生成的令牌本身实现了 ToTokens,因此可以插值到后续的 quote! 调用中,以构建最终结果。

let type_definition = quote! {...};
let methods = quote! {...};

let tokens = quote! {
    #type_definition
    #methods
};

构造标识符

假设我们有一个标识符 ident,它来自宏输入中的某个位置,而我们需要以某种方式对其进行修改,以便用于宏输出。让我们考虑在标识符前添加下划线。

直接将标识符插入到下划线旁边并不会实现拼接效果。下划线和标识符仍会是两个独立的标记,就好像你写了 _ x 一样。

// incorrect
quote! {
    let mut _#ident = 0;
}

解决方法是使用正确的值构建一个新的标识符令牌。由于这是一种常见情况,format_ident! 宏提供了一个便捷的工具来正确执行此操作。

let varname = format_ident!("_{}", ident);
quote! {
    let mut #varname = 0;
}

或者,可以使用 Syn 和 proc-macro2 提供的 API 直接构建标识符。这与上述方法大致等效,但不会处理作为原始标识符的 ident

let concatenated = format!("_{}", ident);
let varname = syn::Ident::new(&concatenated, ident.span());
quote! {
    let mut #varname = 0;
}

进行方法调用

假设我们的宏要求宏输入中指定的某个类型具有名为 new 的构造函数。我们将该类型存储在一个类型为 syn::Type 的变量 field_type 中,并希望调用该构造函数。

// incorrect
quote! {
    let value = #field_type::new();
}

这种情况并非总能奏效。如果 field_typeString,展开后的代码会包含 String::new(),这没有问题。但如果 field_type 是类似 Vec<i32> 这样的类型,展开后的代码就会变成 Vec<i32>::new(),这属于无效语法。通常在手写的 Rust 代码中,我们会写成 Vec::<i32>::new(),不过对于宏而言,以下方式往往更为便捷。

quote! {
    let value = <#field_type>::new();
}

这会展开为 <Vec<i32>>::new(),其行为符合预期。

类似的模式也适用于 trait 方法。

quote! {
    let value = <#field_type as core::default::Default>::default();
}

卫生性

任何插值标记都会保留其 ToTokens 实现所提供的 Span 信息。在 quote! 调用内部生成的标记,其跨度由 Span::call_site() 指定。

可以通过 quote_spanned! 宏显式提供不同的跨度。


非宏代码生成器

当在 build.rs 或 main.rs 中使用 quote 并将输出写入文件时,建议代码生成器在写入前通过 prettyplease 处理标记。这样,若生成的代码中出现错误,便于人工阅读和调试。

请注意,当标记写入文件时,不会保留任何卫生性或跨度信息;从标记到源代码的转换是有损的。

build.rs 中的使用示例:

let output = quote! { ... };
let syntax_tree = syn::parse2(output).unwrap();
let formatted = prettyplease::unparse(&syntax_tree);

let out_dir = env::var_os("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("out.rs");
fs::write(dest_path, formatted).unwrap();

许可协议

根据您的选择,本项目基于 Apache 许可证 2.0 版MIT 许可证 进行许可。
除非您明确另有说明,否则您有意提交以纳入此 crate 的任何贡献(如 Apache-2.0 许可证中所定义)均应按上述方式双重许可,且不附加任何额外条款或条件。

项目介绍

提供用于生成Rust代码的宏。 | A Rust library that provides support for generating Rust code.

定制我的领域