第十三章:宏系统——元编程的艺术
本章导读:想象你在填写表格,每张表格结构相同,只是内容不同。如果有一种方式能自动生成表格,岂不是省去大量重复工作?宏就是这样的"表格生成器"——它写代码的代码。Rust 的宏系统强大而安全,让你在不牺牲类型安全的前提下,实现编译期的代码生成。
🎭 13.1 宏 vs 函数
📊 13.1.1 核心区别
| 特性 | 函数 | 宏 |
|---|---|---|
| 调用时机 | 运行时 | 编译时 |
| 参数数量 | 固定 | 可变 |
| 类型检查 | 定义时 | 展开后 |
| 能否生成代码 | 否 | 是 |
| 递归 | 容易 | 需要技巧 |
// 函数:固定参数,运行时调用
fn add(a: i32, b: i32) -> i32 {
a + b
}
// 宏:可变参数,编译时展开
macro_rules! add {
($a:expr) => { $a };
($a:expr, $($rest:expr),+) => {
$a + add!($($rest),+)
};
}
fn main() {
println!("函数: {}", add(1, 2));
println!("宏: {}", add!(1, 2, 3, 4, 5)); // 可变参数!
}
🎯 13.1.2 宏的应用场景
// 1. 可变参数
println!("Hello");
println!("{} + {} = {}", 1, 2, 3);
// 2. 代码生成
vec![1, 2, 3]; // 展开为 Vec::new() + push
// 3. DSL(领域特定语言)
sqlx::query!("SELECT * FROM users WHERE id = ?", user_id);
// 4. 编译期检查
const _: () = assert!(std::mem::size_of::<usize>() == 8);
📝 13.2 声明式宏:macro_rules!
声明式宏使用模式匹配来生成代码。
🌱 13.2.1 基本语法
macro_rules! 宏名 {
(模式) => { 展开代码 };
// 多个分支
(模式1) => { 代码1 };
(模式2) => { 代码2 };
}
// 调用方式
宏名!(参数);
宏名![参数];
宏名!{参数};
🎯 13.2.2 片段分类符
macro_rules! demo {
// expr: 表达式(1 + 2, func(), etc.)
($e:expr) => { println!("表达式: {}", $e) };
// ident: 标识符(变量名、函数名)
($i:ident) => { println!("标识符: {}", stringify!($i)) };
// ty: 类型(i32, String, Vec<u8>)
($t:ty) => { println!("类型: {}", stringify!($t)) };
// literal: 字面量(1, "hello", 'c')
($l:literal) => { println!("字面量: {}", $l) };
// stmt: 语句(let x = 1;)
($s:stmt) => { println!("语句: {}", stringify!($s)) };
// path: 路径(std::collections::HashMap)
($p:path) => { println!("路径: {}", stringify!($p)) };
// tt: 标记树(任何标记)
($($tt:tt)*) => { println!("标记树: {}", stringify!($($tt)*)) };
// block: 代码块 { ... }
($b:block) => { println!("代码块: {}", stringify!($b)) };
}
fn main() {
demo!(1 + 2); // expr
demo!(my_variable); // ident
demo!(Vec<String>); // ty
demo!(42); // literal
demo!(let x = 1;); // stmt
demo!(std::mem::size_of); // path
demo!({ 1 + 2 }); // block
}
🔄 13.2.3 重复模式
macro_rules! vec_demo {
// 空
() => { Vec::new() };
// 一个元素
($elem:expr) => {
{
let mut v = Vec::new();
v.push($elem);
v
}
};
// 多个元素:$() 包裹重复部分, * 表示0次或多次
($($elem:expr),+ $(,)?) => {
{
let mut v = Vec::new();
$(v.push($elem);)+ // 重复 push
v
}
};
}
fn main() {
let v1 = vec_demo!();
let v2 = vec_demo!(1);
let v3 = vec_demo!(1, 2, 3);
println!("{:?} {:?} {:?}", v1, v2, v3);
}
重复符号说明:
*:0次或多次+:1次或多次?:0次或1次$(...):分组$()后的;或,:分隔符$(,)?:可选的尾随逗号
🔧 13.2.4 实用宏示例
打印变量名和值:
macro_rules! dbg {
($var:ident) => {
println!("{} = {:?}", stringify!($var), $var);
};
($($var:ident),+ $(,)?) => {
$(println!("{} = {:?}", stringify!($var), $var);)+
};
}
fn main() {
let name = "Rust";
let version = 2021;
dbg!(name);
dbg!(name, version);
}
简化结构体构造:
macro_rules! struct_builder {
($name:ident { $($field:ident: $type:ty),* $(,)? }) => {
pub struct $name {
$(pub $field: $type,)*
}
impl $name {
pub fn new() -> Self {
Self {
$($field: Default::default(),)*
}
}
$(
pub fn $field(mut self, value: $type) -> Self {
self.$field = value;
self
}
)*
}
};
}
// 使用宏生成结构体
struct_builder!(Person {
name: String,
age: u32,
});
fn main() {
let person = Person::new()
.name("Alice".to_string())
.age(30);
println!("{} is {}", person.name, person.age);
}
🧪 13.3 过程宏
过程宏是 Rust 函数,接收 TokenStream 作为输入,返回 TokenStream 作为输出。
📦 13.3.1 创建过程宏项目
# 过程宏必须在单独的 crate 中
cargo new my_macros --lib
# Cargo.toml
[lib]
proc-macro = true
[dependencies]
syn = "2.0"
quote = "1.0"
proc-macro2 = "1.0"
🏷️ 13.3.2 派生宏
// my_macros/src/lib.rs
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
#[proc_macro_derive(Hello)]
pub fn hello_derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let expanded = quote! {
impl #name {
pub fn say_hello(&self) {
println!("Hello from {}!", stringify!(#name));
}
}
};
TokenStream::from(expanded)
}
使用派生宏:
// 主项目中
use my_macros::Hello;
#[derive(Hello)]
struct MyStruct {
value: i32,
}
fn main() {
let obj = MyStruct { value: 42 };
obj.say_hello(); // "Hello from MyStruct!"
}
🔧 13.3.3 属性宏
// my_macros/src/lib.rs
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemFn};
#[proc_macro_attribute]
pub fn log_calls(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as ItemFn);
let fn_name = &input.sig.ident;
let fn_block = &input.block;
let expanded = quote! {
fn #fn_name() {
println!("调用函数: {}", stringify!(#fn_name));
#fn_block
println!("函数完成: {}", stringify!(#fn_name));
}
};
TokenStream::from(expanded)
}
使用属性宏:
#[log_calls]
fn do_something() {
println!("执行某些操作...");
}
fn main() {
do_something();
// 输出:
// 调用函数: do_something
// 执行某些操作...
// 函数完成: do_something
}
📞 13.3.4 函数式宏
// my_macros/src/lib.rs
use proc_macro::TokenStream;
use quote::quote;
use syn::parse::{Parse, ParseStream};
use syn::{Ident, LitInt, Token};
struct RepeatArgs {
name: Ident,
times: LitInt,
}
impl Parse for RepeatArgs {
fn parse(input: ParseStream) -> syn::Result<Self> {
let name: Ident = input.parse()?;
input.parse::<Token![,]>()?;
let times: LitInt = input.parse()?;
Ok(RepeatArgs { name, times })
}
}
#[proc_macro]
pub fn repeat(input: TokenStream) -> TokenStream {
let args = syn::parse_macro_input!(input as RepeatArgs);
let name = &args.name;
let times: usize = args.times.base10_parse().unwrap();
let expanded = quote! {
{
for _ in 0..#times {
println!("{}", stringify!(#name));
}
}
};
TokenStream::from(expanded)
}
使用函数式宏:
use my_macros::repeat;
fn main() {
repeat!(Rust, 3);
// 输出:
// Rust
// Rust
// Rust
}
🔬 13.4 宏调试技巧
📋 13.4.1 cargo expand
查看宏展开后的代码:
cargo install cargo-expand
cargo expand
🔍 13.4.2 内置调试宏
macro_rules! debug_macro {
($($tt:tt)*) => {
{
// 打印原始输入
println!("输入: {}", stringify!($($tt)*));
// 在编译时警告
// compile_error!("调试:查看宏展开");
$($tt)*
}
};
}
fn main() {
debug_macro!(let x = 1 + 2;);
// 输出: 输入: let x = 1 + 2
}
🛡️ 13.4.3 卫生性(Hygiene)
Rust 宏是卫生的——宏内定义的变量不会与外部冲突:
macro_rules! confusion {
() => {
let x = 1; // 宏内的 x
println!("宏内 x: {}", x);
};
}
fn main() {
let x = 100; // 外部的 x
confusion!();
println!("外部 x: {}", x); // 仍然是 100
}
第一性原理:宏卫生防止了变量名意外捕获,这是 Rust 宏系统区别于 C 宏的关键特性。
🧪 13.5 实战:构建一个简单的测试框架
让我们用宏创建一个迷你的测试框架:
// test_framework/lib.rs
use std::sync::atomic::{AtomicUsize, Ordering};
static TESTS_REGISTERED: AtomicUsize = AtomicUsize::new(0);
static TESTS_PASSED: AtomicUsize = AtomicUsize::new(0);
// 测试函数类型
type TestFn = fn() -> Result<(), String>;
// 存储测试用例
thread_local! {
static TESTS: std::cell::RefCell<Vec<(&'static str, TestFn)>> =
std::cell::RefCell::new(Vec::new());
}
// 注册测试
pub fn register_test(name: &'static str, test: TestFn) {
TESTS.with(|tests| {
tests.borrow_mut().push((name, test));
});
TESTS_REGISTERED.fetch_add(1, Ordering::SeqCst);
}
// 运行所有测试
pub fn run_tests() {
println!("\n===== 运行测试 =====\n");
TESTS.with(|tests| {
for (name, test) in tests.borrow().iter() {
match test() {
Ok(()) => {
println!("✅ {} ... 通过", name);
TESTS_PASSED.fetch_add(1, Ordering::SeqCst);
}
Err(e) => {
println!("❌ {} ... 失败", name);
println!(" 错误: {}", e);
}
}
}
});
let registered = TESTS_REGISTERED.load(Ordering::SeqCst);
let passed = TESTS_PASSED.load(Ordering::SeqCst);
println!("\n===== 测试结果 =====");
println!("通过: {}/{}", passed, registered);
if passed == registered {
println!("🎉 所有测试通过!");
}
}
// 测试宏
#[macro_export]
macro_rules! test {
($name:ident $block:block) => {
#[allow(non_snake_case)]
fn $name() -> Result<(), String> $block
// 使用 ctor 在 main 前注册(或手动调用)
// 这里简化为手动注册
};
}
// 断言宏
#[macro_export]
macro_rules! assert_ok {
($expr:expr) => {
match $expr {
Ok(v) => v,
Err(e) => return Err(format!("断言失败: {:?}", e)),
}
};
}
#[macro_export]
macro_rules! assert_eq_msg {
($left:expr, $right:expr) => {
if $left != $right {
return Err(format!(
"断言失败: {} != {}",
stringify!($left),
stringify!($right)
));
}
};
}
使用示例:
fn test_addition() -> Result<(), String> {
assert_eq_msg!(1 + 1, 2);
assert_eq_msg!(2 + 2, 4);
Ok(())
}
fn test_division() -> Result<(), String> {
let result = 10 / 2;
assert_eq_msg!(result, 5);
Ok(())
}
fn test_failure_example() -> Result<(), String> {
assert_eq_msg!(1 + 1, 3); // 这会失败
Ok(())
}
fn main() {
// 手动注册测试
register_test("test_addition", test_addition);
register_test("test_division", test_division);
register_test("test_failure", test_failure_example);
run_tests();
}
⚠️ 13.6 宏的最佳实践
✅ 应该用宏的场景
// 1. 消除样板代码
macro_rules! getters {
($($field:ident: $type:ty),*) => {
$(
pub fn $field(&self) -> &$type {
&self.$field
}
)*
};
}
// 2. 创建 DSL
macro_rules! route {
(GET $path:expr => $handler:expr) => {
println!("注册 GET {} -> {}", $path, stringify!($handler));
};
(POST $path:expr => $handler:expr) => {
println!("注册 POST {} -> {}", $path, stringify!($handler));
};
}
// 3. 条件编译
macro_rules! platform_message {
() => {
#[cfg(target_os = "linux")]
println!("Running on Linux");
#[cfg(target_os = "windows")]
println!("Running on Windows");
#[cfg(target_os = "macos")]
println!("Running on macOS");
};
}
❌ 不应该用宏的场景
// 不好:简单的操作用宏
macro_rules! add {
($a:expr, $b:expr) => { $a + $b };
}
// 好:用函数
fn add(a: i32, b: i32) -> i32 {
a + b
}
// 不好:隐藏复杂逻辑
macro_rules! magic {
($x:expr) => {
// 100行复杂代码...
};
}
// 好:使用函数或方法,清晰明了
fn process(x: i32) -> i32 {
// 复杂但可读的逻辑
x * 2
}
费曼技巧提问:为什么宏的代码更难调试?提示:想想编译器看到的代码和你看到的代码有什么区别。
📝 本章小结
本章我们学习了 Rust 的宏系统:
| 宏类型 | 语法 | 用途 |
|---|---|---|
| 声明式宏 | macro_rules! | 模式匹配,代码生成 |
| 派生宏 | #[procmacroderive] | 自动实现 trait |
| 属性宏 | #[procmacroattribute] | 修改/增强项目 |
| 函数式宏 | #[proc_macro] | 自定义语法 |
关键概念:
- TokenStream:标记流,宏操作的基本单位
- 卫生性:宏内变量不污染外部作用域
- 片段分类符:匹配不同类型的代码片段
记住:宏很强大,但应该谨慎使用。能用手写代码解决的问题,就不要用宏。
动手实验:
- 编写一个
hashmap!宏,像vec!一样便捷地创建 HashMap。- 编写一个
measure!宏,测量并打印代码块的执行时间。- 创建一个派生宏
#[derive(Builder)],为结构体生成建造者模式代码。- 解释为什么以下宏会编译失败,如何修复:
``
rust macro_rules! assign { ($var:ident = $val:expr) => { let $var = $val; }; } fn main() { assign!(x = 1); assign!(x = 2); // 错误:重复定义 }``