各编程语言的抽象语法树查看工具: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宏生成一个新的(当前为空)TokenStream
let add_hello_world = quote! {};
// 使用Into trait将此TokenStream更改为具有相同名称的普通/标准库TokenStream
add_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 替代方案