Rust进阶[part6]_宏

Rust进阶[part6]_宏

macro 概述

宏是 Rust 中强大的代码生成工具,与函数不同,它在编译期展开并生成具体代码,能处理可变参数、实现动态逻辑,语法上以 ! 结尾(如 println! vec! panic!)。

常用宏简化了重复操作:println! 处理格式化输出,vec! 快速创建向量,cfg! 做条件编译判断。宏支持模式匹配和代码生成,比函数更灵活,但也因编译期处理,调试和学习门槛稍高。按功能可分为声明宏(基于模式匹配)和过程宏(更复杂的代码生成),是 Rust 元编程的核心能力。

一些之前常用的宏,例如 println!vec! 等,它们简化了代码编写。

声明宏

声明宏允许你通过模式匹配来生成代码。例如,下面是一个简单的加法声明宏:

macro_rules! add {
    ($a:expr, $b:expr) => {
        $a + $b
    };
}

fn main() {
    let result = add!(1, 2);
    println!("Result: {}", result);
}

格式

macro_rules! 宏名称 {
    (模式1) => { 代码1 };
    (模式2) => { 代码2 };
    // ...
}
  • 模式:匹配调用宏时的输入参数。
  • 元变量:用 $ 开头,捕获输入的片段(如表达式、标识符等)。
  • 重复符号*(零或多次)、+(至少一次),类似正则表达式。

过程宏

派生宏

派生宏可以为结构体、枚举等类型自动生成代码。例如,使用 #[derive(Debug)] 可以自动为结构体添加调试输出的能力:

#[derive(Debug)]
struct Person {
    name: String,
    age: u8,
}

fn main() {
    let person = Person {
        name: String::from("Alice"),
        age: 30,
    };
    println!("{:?}", person);
}

属性宏

属性宏可以为函数、结构体等添加自定义属性。例如,自定义一个日志属性宏:

#[proc_macro_attribute]
pub fn log(_attr: TokenStream, item: TokenStream) -> TokenStream {
    // 这里可以实现日志记录逻辑
    item
}

#[log]
fn my_function() {
    println!("Function called");
}

fn main() {
    my_function();
}

函数宏

函数宏类似于普通函数,但在编译时展开。例如:

#[proc_macro]
pub fn greet(_item: TokenStream) -> TokenStream {
    let code = "println!(\"Hello, world!\");";
    code.parse().unwrap()
}

fn main() {
    greet!();
}

总结

宏是 Rust 中强大的代码生成工具,声明宏通过模式匹配生成代码,过程宏则提供了更灵活的代码生成方式,包括派生宏、属性宏和函数宏。

应用场景

  1. 减少重复代码: 通过宏生成重复的代码,提高代码的可维护性。
  2. 编译期计算:在编译器进行计算并生成代码,提高运行时性能。
  3. DSL(领域特定语言):使用宏定义领域特定语言,提高代码的表达力和可读性。

课后习题

实现一下宏

assert_eq!(repeat!("x", 3), "xxx");
    assert_eq!(sum!(1, 2, 3, 4, 5), 15);
    assert_eq!(max_value!(1, 8, 9), 9);

代码:

#[macro_export]
macro_rules! say_hello {
    () => {
        println!("hello!");
    };
    ($name:expr) => {
        println!("hello {}!", $name);
    };
}

#[macro_export]
macro_rules! repeat {
    () => {
        println!("repeat!");
    };
    ($s:expr, $n:expr) => {
        std::string::String::from($s).repeat($n)
    };
}

#[macro_export]
macro_rules! sum {
    () => {
        println!("sum!");
    };
    ($($x:expr),*) => {
        {
            let mut total = 0;
            $(total += $x;)*
            total
        }
    };
}

#[macro_export]
macro_rules! max_value {
    () => {
        println!("max_value!");
    };
    ($x:expr, $y:expr, $z:expr) => {
        if $x > $y {
            if $x > $z { $x } else { $z }
        } else {
            if $y > $z { $y } else { $z }
        }
    };
}

一些符号拆解:

  • $():分组捕获元变量。
  • \*/+:重复匹配输入片段。
  • $(...)\*:在展开时代入重复的代码逻辑。
// sum! 宏展开逻辑
$(total += $x;)*  // 对每个捕获的表达式,生成一次累加语句