各编程语言的抽象语法树查看工具:https://astexplorer.net/
纲要
- 设置过程宏
- 通过解析 tokens 流获得结构体的名称
- 生成硬编码输出
- 在生成的代码中使用变量
- 使用 cargo expand 检查生成的代码
- 在没有 syn 和 quote 帮助的情况下编写宏
- 理解 Rust 内部宏的特殊之处
使用过程宏的两种方式
- 方式一:创建普通的二进制应用程序,然后添加依赖库,最后在 main.rs 中使用宏
- 方式二:自己弄宏和配置
- 在项目目录下(如 hello-world)创建子文件夹(如 hello-world-macro)
- 在子文件夹下创建 lib,在 Cargo.toml 配置 [lib],在 lib.rs 编写宏

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

[package]name = "hello_world"version = "0.1.0"edition = "2021"[dependencies]hello-world-macro = { path = "./hello-world-macro" }
#[macro_use]extern crate hello_world_macro;#[derive(Hello)]struct Example;fn main() {}// 或者use hello_world_macro::Hello;#[derive(Hello)]struct Example;fn main() {}
使用 syn 和 quote 的小示例
use quote::quote;use proc_macro::TokenStream;use syn::{parse_macro_input, DeriveInput};#[proc_macro_derive(Hello)]pub fn hello(item: TokenStream) -> TokenStream {let ast = parse_macro_input!(item as DeriveInput);let name = ast.ident;let add_hello_world = quote! {impl #name {fn hello_world(&self) {println!("Hello, world!");}}};add_hello_world.into()}
#[macro_use]extern crate hello_world_macro;#[derive(Hello)]struct Example;fn main() {let e = Example {};e.hello_world();}
Cargo expand 的使用
- https://github.com/dtolnay/cargo-expand
- cargo install cargo-expand


不使用 syn 和 quote 小示例
use proc_macro::{TokenStream, TokenTree};#[proc_macro_derive(Hello)]pub fn hello(item: TokenStream) -> TokenStream {fn ident_name(item: TokenTree) -> String {match item {TokenTree::Ident(i) => i.to_string(),_ => panic!("not an ident"),}}let name = ident_name(item.into_iter().nth(1).unwrap());format!("impl {} {{ fn hello_world(&self) {{ println!(\"Hello, world!\") }}}} ", name).parse().unwrap()}
有几个库试图提供一个轻量级的 syn 替代方案

