用户工具

站点工具


rust:第十六章宏

第十六章 宏

宏(Macro)是Rust中一种强大的元编程工具,允许你编写生成代码的代码。Rust有两种类型的宏:声明宏(declarative macros)和过程宏(procedural macros)。本章将详细介绍这两种宏的工作原理和使用方法。

1. 宏概述

1.1 什么是宏

宏是一种代码生成机制,在编译时展开为实际的Rust代码。与函数不同,宏可以:

  • 接收可变数量的参数
  • 在编译时生成代码
  • 操作Rust的抽象语法树(AST)

1.2 宏与函数的区别

特性 函数
参数数量 固定 可变
执行时机 运行时 编译时
类型检查 严格 模式匹配
代码生成
// 函数 - 固定参数
fn add(a: i32, b: i32) -> i32 {
    a + b
}
 
// 宏 - 可变参数
println!("{} {}", 1, 2);
println!("{} {} {}", 1, 2, 3);
vec![1, 2, 3, 4, 5];  // 任意数量的元素

2. 声明宏(macro_rules!)

声明宏使用 macro_rules! 定义,是最常见的宏类型。

2.1 简单的宏定义

// 定义一个简单的宏
macro_rules! say_hello {
    () => {
        println!("Hello!");
    };
}
 
fn main() {
    say_hello!();  // 输出: Hello!
}

2.2 带参数的宏

macro_rules! print_twice {
    ($x:expr) => {
        println!("{}", $x);
        println!("{}", $x);
    };
}
 
fn main() {
    print_twice!("Rust");
    print_twice!(42);
}

2.3 多个匹配模式

macro_rules! greet {
    // 无参数
    () => {
        println!("Hello!");
    };
    // 一个参数
    ($name:expr) => {
        println!("Hello, {}!", $name);
    };
    // 两个参数
    ($greeting:expr, $name:expr) => {
        println!("{}, {}!", $greeting, $name);
    };
}
 
fn main() {
    greet!();
    greet!("World");
    greet!("Good morning", "Alice");
}

2.4 重复模式

使用 $(...)* 或 $(…)+ 匹配重复的模式。

macro_rules! vector {
    ($($x:expr),*) => {
        {
            let mut temp_vec = Vec::new();
            $(
                temp_vec.push($x);
            )*
            temp_vec
        }
    };
}
 
fn main() {
    let v = vector![1, 2, 3, 4, 5];
    println!("{:?}", v);
 
    let empty: Vec<i32> = vector![];
    println!("{:?}", empty);
}

2.5 类似vec!的宏

macro_rules! my_vec {
    // vec![elem; len]
    ($elem:expr; $n:expr) => {
        std::vec::from_elem($elem, $n)
    };
    // vec![a, b, c]
    ($($x:expr),+ $(,)?) => {
        <[_]>::into_vec(Box::new([$($x),+]))
    };
}
 
fn main() {
    let v1 = my_vec![0; 5];
    println!("{:?}", v1);  // [0, 0, 0, 0, 0]
 
    let v2 = my_vec![1, 2, 3];
    println!("{:?}", v2);  // [1, 2, 3]
}

3. 捕获类型

3.1 常见的捕获类型

捕获类型 描述 示例
expr 表达式 1 + 2, “hello”
stmt 语句(不含分号) let x = 5
block 代码块 { let x = 1; x + 2 }
pat 模式 Some(x), (a, b)
ty 类型 i32, Vec<String>
ident 标识符 foo, Bar
path 路径 std::mem::drop
tt token树 任意token序列
item fn, struct, use等

3.2 使用不同捕获类型

macro_rules! create_function {
    // ident捕获函数名
    ($func_name:ident) => {
        fn $func_name() {
            println!("你调用了 {:?}", stringify!($func_name));
        }
    };
}
 
create_function!(foo);
create_function!(bar);
 
fn main() {
    foo();
    bar();
}

3.3 类型捕获

macro_rules! impl_display {
    ($t:ty) => {
        impl std::fmt::Display for $t {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                write!(f, "{}", self.0)
            }
        }
    };
}
 
struct Wrapper(i32);
impl_display!(Wrapper);
 
fn main() {
    let w = Wrapper(42);
    println!("{}", w);  // 42
}

4. 高级宏技巧

4.1 重复分隔符

macro_rules! sum {
    // 最后一个逗号是可选的
    ($($x:expr),+ $(,)?) => {
        0 $(+ $x)*
    };
}
 
fn main() {
    println!("{}", sum!(1, 2, 3));
    println!("{}", sum!(1, 2, 3,));  // 末尾逗号也可以
}

4.2 重复索引

macro_rules! tuple_to_array {
    ($($x:expr),+ $(,)?) => {
        [$(($x, stringify!($x))),+]
    };
}
 
fn main() {
    let arr = tuple_to_array!(1, 2, 3);
    for (val, name) in arr {
        println!("{} 的名字是 {}", val, name);
    }
}

4.3 嵌套重复

macro_rules! matrix {
    ($($($x:expr),+);+ $(;)?) => {
        vec![$(vec![$($x),+]),+]
    };
}
 
fn main() {
    let m = matrix!(
        1, 2, 3;
        4, 5, 6;
        7, 8, 9
    );
    println!("{:?}", m);
}

4.4 递归宏

macro_rules! calculate {
    // 基本情况
    (eval $e:expr) => {{
        let val: i32 = $e;
        println!("{} = {}", stringify!($e), val);
        val
    }};
 
    // 递归展开
    (eval $e:expr $(, $rest:expr)+) => {{
        calculate!(eval $e);
        calculate!(eval $($rest),+)
    }};
}
 
fn main() {
    calculate!(eval 1 + 2, 3 * 4, 10 / 2);
}

5. 卫生宏(Hygienic Macros)

Rust的宏是卫生的,意味着宏内部定义的变量不会与外部变量冲突。

macro_rules! define_x {
    () => {
        let x = 42;
        println!("宏内部: x = {}", x);
    };
}
 
fn main() {
    let x = 10;
    define_x!();
    println!("外部: x = {}", x);  // 仍然是10,没有被宏改变
}

如果需要访问外部变量,必须作为参数传入:

macro_rules! set_value {
    ($var:ident, $val:expr) => {
        $var = $val;
    };
}
 
fn main() {
    let mut x = 10;
    set_value!(x, 42);
    println!("x = {}", x);  // 42
}

6. 过程宏(Procedural Macros)

过程宏更强大也更复杂,它们直接操作Rust的抽象语法树(AST)。

6.1 过程宏类型

  • 自定义派生(Derive macro):为struct和enum添加derive属性
  • 属性宏(Attribute macro):定义新的属性
  • 函数式宏(Function-like macro):看起来像函数调用的宏

6.2 创建过程宏项目

过程宏必须定义在单独的crate中:

cargo new my_derive --lib

编辑 Cargo.toml:

[package]
name = "my_derive"
version = "0.1.0"
edition = "2021"
 
[lib]
proc-macro = true
 
[dependencies]
proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "2.0", features = ["full"] }

6.3 自定义Derive宏

// my_derive/src/lib.rs
use proc_macro::TokenStream;
use quote::quote;
use syn;
 
#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
    // 解析输入为DeriveInput
    let ast = syn::parse(input).unwrap();
 
    // 构建trait实现
    impl_hello_macro(&ast)
}
 
fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {
    let name = &ast.ident;
    let gen = quote! {
        impl HelloMacro for #name {
            fn hello_macro() {
                println!("Hello, Macro! 我是 {}", stringify!(#name));
            }
        }
    };
    gen.into()
}

使用:

// 主项目
use my_derive::HelloMacro;
 
trait HelloMacro {
    fn hello_macro();
}
 
#[derive(HelloMacro)]
struct Pancakes;
 
fn main() {
    Pancakes::hello_macro();  // Hello, Macro! 我是 Pancakes
}

6.4 带属性的Derive宏

// my_derive/src/lib.rs
use proc_macro::TokenStream;
use quote::quote;
use syn::{self, parse::Parser};
 
#[proc_macro_derive(Builder, attributes(builder))]
pub fn builder_derive(input: TokenStream) -> TokenStream {
    let ast = syn::parse(input).unwrap();
    impl_builder(&ast)
}
 
fn impl_builder(ast: &syn::DeriveInput) -> TokenStream {
    let name = &ast.ident;
    let builder_name = quote::format_ident!("{}Builder", name);
 
    let gen = quote! {
        pub struct #builder_name;
 
        impl #name {
            pub fn builder() -> #builder_name {
                #builder_name
            }
        }
    };
    gen.into()
}

6.5 属性宏

属性宏用于定义自定义属性,可以应用于函数、结构体等。

// my_derive/src/lib.rs
use proc_macro::TokenStream;
use quote::quote;
 
#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
    // attr 是属性参数,如 route("GET", "/")
    // item 是被装饰的项
    let method = attr.to_string();
 
    let input = syn::parse_macro_input!(item as syn::ItemFn);
    let name = &input.sig.ident;
    let body = &input.block;
 
    let expanded = quote! {
        #[allow(dead_code)]
        fn #name() {
            println!("路由方法: {}", #method);
            #body
        }
    };
 
    expanded.into()
}

使用:

use my_derive::route;
 
#[route("GET", "/")]
fn index() {
    println!("首页");
}
 
fn main() {
    index();
}

6.6 函数式过程宏

函数式过程宏类似于 macro_rules!,但使用Rust代码实现。

// my_derive/src/lib.rs
use proc_macro::TokenStream;
use quote::quote;
use syn;
 
#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {
    // 解析SQL语句
    let sql = input.to_string();
 
    let expanded = quote! {
        println!("执行SQL: {}", #sql);
    };
 
    expanded.into()
}

使用:

use my_derive::sql;
 
fn main() {
    sql!(SELECT * FROM users WHERE id = 1);
}

7. 高级过程宏技巧

7.1 解析结构体字段

use proc_macro::TokenStream;
use quote::quote;
use syn::{self, Data, Fields};
 
#[proc_macro_derive(Getters)]
pub fn getters_derive(input: TokenStream) -> TokenStream {
    let ast = syn::parse(input).unwrap();
    impl_getters(&ast)
}
 
fn impl_getters(ast: &syn::DeriveInput) -> TokenStream {
    let name = &ast.ident;
 
    let fields = match &ast.data {
        Data::Struct(data) => match &data.fields {
            Fields::Named(fields) => &fields.named,
            _ => panic!("只支持命名字段"),
        },
        _ => panic!("只支持结构体"),
    };
 
    let getters = fields.iter().map(|f| {
        let field_name = f.ident.as_ref().unwrap();
        let field_ty = &f.ty;
        let getter_name = quote::format_ident!("get_{}", field_name);
 
        quote! {
            pub fn #getter_name(&self) -> &#field_ty {
                &self.#field_name
            }
        }
    });
 
    let gen = quote! {
        impl #name {
            #(#getters)*
        }
    };
 
    gen.into()
}

7.2 错误处理

use proc_macro::TokenStream;
use quote::quote;
use syn::{self, spanned::Spanned};
 
#[proc_macro_derive(MyTrait)]
pub fn my_trait_derive(input: TokenStream) -> TokenStream {
    let ast = syn::parse(input).unwrap();
 
    match impl_my_trait(&ast) {
        Ok(tokens) => tokens,
        Err(e) => e.to_compile_error().into(),
    }
}
 
fn impl_my_trait(ast: &syn::DeriveInput) -> Result<TokenStream, syn::Error> {
    // 检查是否实现了Clone
    let has_clone = ast.attrs.iter().any(|attr| {
        attr.path().is_ident("derive") && 
        attr.parse_args::<syn::Ident>().map(|i| i == "Clone").unwrap_or(false)
    });
 
    if !has_clone {
        return Err(syn::Error::new(
            ast.span(),
            "MyTrait需要实现Clone"
        ));
    }
 
    let name = &ast.ident;
    Ok(quote! {
        impl MyTrait for #name {}
    }.into())
}

8. 编译时计算

8.1 const fn 与宏

// const fn在编译时执行
const fn fibonacci(n: u64) -> u64 {
    match n {
        0 => 0,
        1 => 1,
        _ => fibonacci(n - 1) + fibonacci(n - 2),
    }
}
 
fn main() {
    // 在编译时计算
    const FIB_10: u64 = fibonacci(10);
    println!("斐波那契数列第10项: {}", FIB_10);
}

8.2 include!宏

// 包含其他文件的内容
include!("some_generated_code.rs");
 
// 包含文件内容为字符串
const CONFIG: &str = include_str!("config.toml");
 
// 包含文件内容为字节数组
const BINARY: &[u8] = include_bytes!("data.bin");

9. 条件编译

9.1 cfg属性

// 只在测试时编译
#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }
}
 
// 只在特定操作系统编译
#[cfg(target_os = "linux")]
fn linux_only() {
    println!("Linux特定代码");
}
 
// 多条件
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
fn linux_x86_64_only() {}
 
// 任条件
#[cfg(any(target_os = "linux", target_os = "macos"))]
fn unix_like() {}
 
// 排除条件
#[cfg(not(target_os = "windows"))]
fn not_windows() {}

9.2 cfg宏

fn main() {
    if cfg!(target_os = "linux") {
        println!("运行在Linux上");
    } else if cfg!(target_os = "windows") {
        println!("运行在Windows上");
    } else {
        println!("运行在其他系统上");
    }
}

10. 编译时断言

10.1 const_assert

// 使用static_assertions crate
use static_assertions::const_assert;
 
const_assert!(std::mem::size_of::<usize>() == 8);  // 64位系统
 
fn main() {
    println!("编译时断言通过!");
}

10.2 编译时检查

// 使用const panic进行编译时检查
const fn check_size<T>() {
    assert!(std::mem::size_of::<T>() <= 128, "类型太大");
}
 
struct SmallStruct([u8; 64]);
// struct LargeStruct([u8; 256]);  // 编译错误
 
const _: () = check_size::<SmallStruct>();
 
fn main() {}

11. 实际应用案例

11.1 实现自定义vec!

macro_rules! my_vec {
    // vec![value; count]
    ($val:expr; $count:expr) => {{
        let count = $count;
        let mut vec = Vec::with_capacity(count);
        for _ in 0..count {
            vec.push($val);
        }
        vec
    }};
 
    // vec![a, b, c]
    ($($val:expr),* $(,)?) => {{
        let mut vec = Vec::new();
        $(vec.push($val);)*
        vec
    }};
}
 
fn main() {
    let v1 = my_vec![0; 5];
    println!("{:?}", v1);  // [0, 0, 0, 0, 0]
 
    let v2 = my_vec![1, 2, 3, 4, 5];
    println!("{:?}", v2);  // [1, 2, 3, 4, 5]
}

11.2 实现HashMap初始化宏

macro_rules! hashmap {
    () => {
        ::std::collections::HashMap::new()
    };
    ($($key:expr => $value:expr),* $(,)?) => {{
        let mut map = ::std::collections::HashMap::new();
        $(
            map.insert($key, $value);
        )*
        map
    }};
}
 
fn main() {
    let map = hashmap! {
        "name" => "Alice",
        "age" => "30",
    };
 
    println!("{:?}", map);
}

11.3 实现类似try!的错误处理宏

macro_rules! try_or_return {
    ($expr:expr) => {
        match $expr {
            Ok(val) => val,
            Err(e) => {
                eprintln!("错误: {}", e);
                return;
            }
        }
    };
    ($expr:expr, $ret:expr) => {
        match $expr {
            Ok(val) => val,
            Err(e) => {
                eprintln!("错误: {}", e);
                return $ret;
            }
        }
    };
}
 
fn may_fail() -> Result<i32, &'static str> {
    Ok(42)
}
 
fn process() -> i32 {
    let val = try_or_return!(may_fail(), -1);
    val * 2
}
 
fn main() {
    println!("结果: {}", process());
}

12. 调试宏

12.1 展开宏查看结果

使用 cargo expand 查看宏展开后的代码:

cargo install cargo-expand
cargo expand

12.2 编译错误定位

// 使用compile_error!产生编译错误
#[cfg(not(feature = "required_feature"))]
compile_error!("必须启用required_feature特性");
 
fn main() {}

12.3 日志调试宏

macro_rules! debug_var {
    ($var:ident) => {
        eprintln!("[DEBUG] {} = {:?} (在{}:{})", 
            stringify!($var), 
            $var,
            file!(),
            line!()
        );
    };
}
 
fn main() {
    let x = 42;
    debug_var!(x);
}

13. 最佳实践

  1. 从简单开始:先用 macro_rules!,必要时再用过程宏
  2. 文档化:为宏提供清晰的文档和示例
  3. 测试边界情况:宏的边缘行为可能难以预测
  4. 使用 $crate:避免路径问题
  5. 遵循命名约定:宏名通常使用 snake_case,过程宏使用 PascalCase
// 使用$crate确保路径正确
#[macro_export]
macro_rules! my_macro {
    () => {
        $crate::internal_function()
    };
}

小结

本章深入探讨了Rust的宏系统:

  • 声明宏(macro_rules!):模式匹配,适合简单的代码生成
  • 过程宏:操作AST,适合复杂的自定义derive
  • Derive宏:为类型自动实现trait
  • 属性宏:添加自定义属性
  • 函数式宏:强大的代码转换能力

宏是Rust元编程的核心,合理使用可以大大提高代码的复用性和可维护性。虽然学习曲线较陡,但掌握宏的使用将使你能够编写更加优雅和强大的Rust代码。

rust/第十六章宏.txt · 最后更改: 127.0.0.1