Day 27: 宏系统
宏(Macro)是Rust中强大的元编程工具。宏在编译时展开,可以帮助我们减少重复代码、增强抽象能力。Rust的宏系统分为两大类:声明式宏(declarative macros)和过程宏(procedural macros)。
今天我们重点学习最常用的声明式宏macro_rules!。
2. 声明式宏基础
Section titled “2. 声明式宏基础”声明式宏使用macro_rules!定义,本质上是一个模式匹配系统:
macro_rules! my_macro { // 模式 => 展开 () => { // 当调用my_macro!()时,展开为这个块 println!("空参数调用"); };}
fn main() { my_macro!();}宏调用的语法是!后跟括号,这与函数调用不同,可以一眼区分。
3. 带参数的宏
Section titled “3. 带参数的宏”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类型来匹配不同语法元素。
4. 重复模式:$(…)*
Section titled “4. 重复模式:$(…)*”宏的强大之处在于可以处理重复的模式:
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!();}$(...)*表示匹配零个或多个符合模式的项,重复的内容会被展开。
5. 综合示例:vec!宏
Section titled “5. 综合示例:vec!宏”标准库的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);}6. 声明式宏的卫生性
Section titled “6. 声明式宏的卫生性”Rust宏是”卫生的”(hygienic),意味着宏展开后的代码不会意外污染外部作用域:
macro_rules! foo { () => { let x = 5; };}
fn main() { foo!(); // 宏内部定义的x不会影响外部 let x = 10; // 这是合法的 println!("x = {}", x);}但这也有例外:宏可以使用$crate来引用外部符号,以及使用macro_rules中的特定技巧来”泄漏”标识符。
7. 过程宏简介
Section titled “7. 过程宏简介”过程宏是另一种宏,它们像函数一样接受代码输入并产生代码输出。主要有三种类型:
- Function-like macros:类似
macro_rules!调用语法 - Derive macros:为struct/enum/union自动生成impl
- 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()}8. derive宏示例:自动实现Debug
Section titled “8. derive宏示例:自动实现Debug”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的实现。
9. 使用dprint或syn/quote处理宏
Section titled “9. 使用dprint或syn/quote处理宏”编写过程宏通常需要借助帮助库:
- 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()}10. 总结
Section titled “10. 总结”今天我们学习了Rust的宏系统:
- 声明式宏
macro_rules!是基于模式匹配代码生成器 - Fragment specifiers 如
:expr、:stmt、:pat等用于匹配不同语法 - 重复
$(...)*语法用于处理可变数量的参数 - 过程宏 提供了更强大的代码转换能力
- Derive宏 是最常用的过程宏类型,用于自动实现trait
宏是Rust抽象层次的重要工具,但也要避免过度使用导致代码难以理解。