各编程语言抽象语法树查看工具https://astexplorer.net/

纲要

  • 设置过程宏
  • 通过解析 tokens 流获得结构体的名称
  • 生成硬编码输出
  • 在生成的代码中使用变量
  • 使用 cargo expand 检查生成的代码
  • 在没有 syn 和 quote 帮助的情况下编写宏
  • 理解 Rust 内部宏的特殊之处

使用过程宏的两种方式

  1. 方式一:创建普通的二进制应用程序,然后添加依赖库,最后在 main.rs 中使用宏
  2. 方式二:自己弄宏和配置
    1. 在项目目录下(如 hello-world)创建子文件夹(如 hello-world-macro
    2. 在子文件夹下创建 lib,在 Cargo.toml 配置 [lib],lib.rs 编写宏

过程宏:Derive macros - 图1

  1. use quote::quote;
  2. use proc_macro::TokenStream;
  3. #[proc_macro_derive(Hello)]
  4. pub fn hello(_item: TokenStream) -> TokenStream {
  5. // 使用quote宏生成一个新的(当前为空)TokenStream
  6. let add_hello_world = quote! {};
  7. // 使用Into trait将此TokenStream更改为具有相同名称的普通/标准库TokenStream
  8. add_hello_world.into()
  9. }

过程宏:Derive macros - 图2

  1. [package]
  2. name = "hello_world"
  3. version = "0.1.0"
  4. edition = "2021"
  5. [dependencies]
  6. hello-world-macro = { path = "./hello-world-macro" }
  1. #[macro_use]
  2. extern crate hello_world_macro;
  3. #[derive(Hello)]
  4. struct Example;
  5. fn main() {}
  6. // 或者
  7. use hello_world_macro::Hello;
  8. #[derive(Hello)]
  9. struct Example;
  10. fn main() {}

使用 syn 和 quote 的小示例

  1. use quote::quote;
  2. use proc_macro::TokenStream;
  3. use syn::{parse_macro_input, DeriveInput};
  4. #[proc_macro_derive(Hello)]
  5. pub fn hello(item: TokenStream) -> TokenStream {
  6. let ast = parse_macro_input!(item as DeriveInput);
  7. let name = ast.ident;
  8. let add_hello_world = quote! {
  9. impl #name {
  10. fn hello_world(&self) {
  11. println!("Hello, world!");
  12. }
  13. }
  14. };
  15. add_hello_world.into()
  16. }
  1. #[macro_use]
  2. extern crate hello_world_macro;
  3. #[derive(Hello)]
  4. struct Example;
  5. fn main() {
  6. let e = Example {};
  7. e.hello_world();
  8. }

Cargo expand 的使用

过程宏:Derive macros - 图3

过程宏:Derive macros - 图4

不使用 syn 和 quote 小示例

  1. use proc_macro::{TokenStream, TokenTree};
  2. #[proc_macro_derive(Hello)]
  3. pub fn hello(item: TokenStream) -> TokenStream {
  4. fn ident_name(item: TokenTree) -> String {
  5. match item {
  6. TokenTree::Ident(i) => i.to_string(),
  7. _ => panic!("not an ident"),
  8. }
  9. }
  10. let name = ident_name(item.into_iter().nth(1).unwrap());
  11. format!("impl {} {{ fn hello_world(&self) {{ println!(\"Hello, world!\") }}}} ", name).parse().unwrap()
  12. }

有几个库试图提供一个轻量级的 syn 替代方案

过程宏:Derive macros - 图5