Skip to content

Day 27: 宏系统

宏(Macro)是Rust中强大的元编程工具。宏在编译时展开,可以帮助我们减少重复代码、增强抽象能力。Rust的宏系统分为两大类:声明式宏(declarative macros)和过程宏(procedural macros)。

今天我们重点学习最常用的声明式宏macro_rules!。

声明式宏使用macro_rules!定义,本质上是一个模式匹配系统:

macro_rules! my_macro {
// 模式 => 展开
() => {
// 当调用my_macro!()时,展开为这个块
println!("空参数调用");
};
}
fn main() {
my_macro!();
}

宏调用的语法是!后跟括号,这与函数调用不同,可以一眼区分。

macro_rules! print_value {
($val:expr) => {
// $val:expr 匹配任何表达式
println!("值是: {}", $val);
};
($name:expr, $val:expr) => {
// 两个表达式
println!("{} = {}", $name, $val);
};
}
fn main() {
let x = 5;
print_value!(x);
print_value!("count", 42);
let s = String::from("hello");
print_value!(s);
}

$val:expr中的:expr是fragment specifier(片段指定符),表示匹配表达式类型。Rust提供多种fragment类型来匹配不同语法元素。

宏的强大之处在于可以处理重复的模式:

macro_rules! print_all {
// $(...)* 匹配零个或多个
($($val:expr),*) => {
$(
println!("值: {}", $val);
)*
};
// 带分隔符的版本
($($val:expr),* $(,)?) => {
$(
println!("值: {}", $val);
)*
};
}
fn main() {
print_all!(1, 2, 3);
print_all!(1, 2, 3,);
print_all!();
}

$(...)*表示匹配零个或多个符合模式的项,重复的内容会被展开。

标准库的vec!宏就是一个声明式宏:

// vec!的简化实现
macro_rules! my_vec {
// 零个元素
() => {
Vec::new()
};
// 一个或多个元素
($($val:expr),* $(,)?) => {
{
// 预先计算容量以优化性能
let mut v = Vec::with_capacity($($val).len());
$(
v.push($val);
)*
v
}
};
}
fn main() {
let v = my_vec![1, 2, 3, 4];
println!("{:?}", v);
let empty: Vec<i32> = my_vec!();
println!("{:?}", empty);
}

Rust宏是”卫生的”(hygienic),意味着宏展开后的代码不会意外污染外部作用域:

macro_rules! foo {
() => {
let x = 5;
};
}
fn main() {
foo!();
// 宏内部定义的x不会影响外部
let x = 10; // 这是合法的
println!("x = {}", x);
}

但这也有例外:宏可以使用$crate来引用外部符号,以及使用macro_rules中的特定技巧来”泄漏”标识符。

过程宏是另一种宏,它们像函数一样接受代码输入并产生代码输出。主要有三种类型:

  1. Function-like macros:类似macro_rules!调用语法
  2. Derive macros:为struct/enum/union自动生成impl
  3. Attribute macros:为条目(item)添加属性

过程宏需要创建单独的crate(proc-macro crate type)。

// 一个简单的derive宏示例
use proc_macro::TokenStream;
#[proc_macro_derive(Hello)]
pub fn hello_derive(input: TokenStream) -> TokenStream {
// 这里可以解析input并生成新的TokenStream
"impl Hello for Test {}".parse().unwrap()
}

derive宏是Rust最常用的过程宏类型。#[derive(Debug)]就是一个derive宏:

#[derive(Debug)]
struct Point {
x: f64,
y: f64,
}
fn main() {
let p = Point { x: 1.0, y: 2.0 };
println!("{:?}", p);
}

编译器会为Point自动生成Debug trait的实现。

编写过程宏通常需要借助帮助库:

  • syn:解析Rust代码为语法树
  • quote:将语法树转回代码
[dependencies]
syn = "2"
quote = "1"
use quote::quote;
use syn;
#[proc_macro_derive(MyTrait)]
pub fn my_trait_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let ast = syn::parse_macro_input!(input as syn::DeriveInput);
let name = &ast.ident;
let gen = quote! {
impl MyTrait for #name {
fn describe(&self) -> String {
format!("这是 {}", stringify!(#name))
}
}
};
gen.into()
}

今天我们学习了Rust的宏系统:

  1. 声明式宏 macro_rules! 是基于模式匹配代码生成器
  2. Fragment specifiers 如:expr、:stmt、:pat等用于匹配不同语法
  3. 重复 $(...)* 语法用于处理可变数量的参数
  4. 过程宏 提供了更强大的代码转换能力
  5. Derive宏 是最常用的过程宏类型,用于自动实现trait

宏是Rust抽象层次的重要工具,但也要避免过度使用导致代码难以理解。