首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >37-Rust 教程 - 宏编程

37-Rust 教程 - 宏编程

作者头像
LarryLan
发布2026-06-08 14:04:57
发布2026-06-08 14:04:57
00
举报

宏编程

代码生成代码:当程序员开始"偷懒",元编程就诞生了

🎬 引入

你有没有写过这样的代码:

代码语言:javascript
复制
fn print_i32(x: i32) {
    println!("i32: {}", x);
}

fn print_i64(x: i64) {
    println!("i64: {}", x);
}

fn print_u32(x: u32) {
    println!("u32: {}", x);
}

fn print_u64(x: u64) {
    println!("u64: {}", x);
}

// ... 继续写到第 20 个类型

写到手酸,然后心想:"这些函数除了类型不一样,其他都一模一样啊!能不能让编译器帮我生成?"

恭喜你,你已经有了元编程的思维!

在 Rust 中,这个超能力叫做 宏(Macro)。宏可以帮你:

  • 自动生成重复代码
  • 创建 DSL(领域特定语言)
  • 在编译时执行计算
  • 实现一些函数做不到的事情

但宏也是 Rust 中最难掌握的特性之一。有人说:"宏是 Rust 的黑魔法。"

今天,咱们就来揭开这层黑魔法的神秘面纱。

📌 核心概念:宏是什么?

宏 vs 函数

先搞清楚一个核心区别:

函数:在运行时被调用,操作的是 :在编译时被展开,操作的是代码本身

想象一下:

  • 函数 就像一个厨师,给你做一道菜(返回值)
  • 就像一个菜谱生成器,帮你写出做菜的步骤(生成代码)

宏的两种类型

Rust 有两种宏:

  1. Declarative Macros(声明式宏)
    • macro_rules! 定义
    • 类似模式匹配
    • 更常用,更简单
    • 今天重点讲
  2. Procedural Macros(过程宏)
    • 用过程函数定义
    • 更强大,更灵活
    • 用于 derive、attribute、function-like
    • 下篇详细讲

宏的名字为什么有 !

这是 Rust 的约定:宏的名字后面加 !,提醒你:

"嘿,这不是普通函数,这是宏!展开时机不一样哦!"

常见的宏:

  • println! - 格式化输出
  • vec! - 创建 Vec
  • format! - 格式化字符串
  • panic! - 触发 panic
  • todo! - 标记待实现

💻 代码示例

1. 第一个 macro_rules! 宏

让我们从最简单的开始:

代码语言:javascript
复制
// 定义一个宏
macro_rules! say_hello {
    () => {
        println!("Hello, Macro!");
    };
}

fn main() {
    say_hello!();  // 输出:Hello, Macro!
}

宏展开后相当于:

代码语言:javascript
复制
fn main() {
    println!("Hello, Macro!");
}

2. 带参数的宏

宏的强大之处在于可以接受参数:

代码语言:javascript
复制
macro_rules! print_value {
    ($value:expr) => {
        println!("Value: {}", $value);
    };
}

fn main() {
    print_value!();           // Value: 42
    print_value!("Hello");      // Value: Hello
    print_value!(vec![, , ]); // Value: [1, 2, 3]
}

语法解释:

  • value - 参数名(以开头)
  • :expr - 匹配器类型(expression)
  • => - 宏展开的规则

3. 多个匹配规则

宏可以有多个规则,类似 match:

代码语言:javascript
复制
macro_rules! print_type {
    // 匹配 i32 类型
    ($value:expr, i32) => {
        println!("i32: {}", $value);
    };
    
    // 匹配 String 类型
    ($value:expr, String) => {
        println!("String: {}", $value);
    };
    
    // 默认规则
    ($value:expr, $type:ty) => {
        println!("{}: {}", stringify!($type), $value);
    };
}

fn main() {
    print_type!(, i32);      // i32: 42
    print_type!("Hi", String); // String: Hi
    print_type!(3.14, f64);    // f64: 3.14
}

常用匹配器类型:

匹配器

匹配内容

示例

:expr

表达式

1 + 2, func()

:ident

标识符

x, my_var

:literal

字面量

42, "hello"

:ty

类型

i32, Vec<String>

:pat

模式

Some(x), _

:stmt

语句

let x = 1;

:tt

Token Tree

任意 token

4. 重复匹配(宏的真正威力)

宏可以匹配重复的模式,这是函数做不到的:

代码语言:javascript
复制
macro_rules! vector {
    // 匹配零个或多个元素
    ($($elem:expr),*) => {
        {
            let mut vec = Vec::new();
            $(
                vec.push($elem);
            )*
            vec
        }
    };
}

fn main() {
    let v1 = vector![];              // 空 Vec
    let v2 = vector![, , ];       // Vec![1, 2, 3]
    let v3 = vector!["a", "b", "c"]; // Vec!["a", "b", "c"]
    
    println!("{:?}", v2);  // [1, 2, 3]
}

语法解释:

  • (elem:expr),* - 匹配逗号分隔的表达式列表
  • $(...)* - 重复展开
  • * - 零次或多次(还有 + 一次或多次,? 零次或一次)

5. 实际案例:实现简化版的 vec!

代码语言:javascript
复制
macro_rules! my_vec {
    // 空的情况
    () => {
        Vec::new()
    };
    
    // 有元素的情况
    ($($elem:expr),+) => {
        {
            let mut temp_vec = Vec::new();
            $(
                temp_vec.push($elem);
            )+
            temp_vec
        }
    };
}

fn main() {
    let v1: Vec<i32> = my_vec![];
    let v2 = my_vec![, , , , ];
    let v3 = my_vec!["hello", "world"];
    
    println!("v2: {:?}", v2);  // [1, 2, 3, 4, 5]
}

6. 创建 DSL:SQL 查询宏

宏可以用来创建领域特定语言(DSL):

代码语言:javascript
复制
macro_rules! query {
    // SELECT * FROM table
    (SELECT * FROM $table:ident) => {
        format!("SELECT * FROM {}", stringify!($table))
    };
    
    // SELECT columns FROM table WHERE condition
    (SELECT $($cols:ident),+ FROM $table:ident WHERE $where:ident = $value:expr) => {
        format!(
            "SELECT {} FROM {} WHERE {} = {:?}",
            stringify!($($cols),+),
            stringify!($table),
            stringify!($where),
            $value
        )
    };
}

fn main() {
    let q1 = query!(SELECT * FROM users);
    println!("{}", q1);
    // SELECT * FROM users
    
    let q2 = query!(SELECT id, name FROM users WHERE age = );
    println!("{}", q2);
    // SELECT id, name FROM users WHERE age = 18
}

7. 实现 derive 宏的简化版

代码语言:javascript
复制
macro_rules! impl_debug {
    ($($struct_name:ident { $($field:ident),* }),*) => {
        $(
            impl std::fmt::Debug for $struct_name {
                fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
                    write!(
                        f,
                        "{} {{ {} }}",
                        stringify!($struct_name),
                        $(
                            format!("{}: {:?}", stringify!($field), self.$field)
                        ).join(", ")
                    )
                }
            }
        )*
    };
}

// 定义结构体
struct Point {
    x: i32,
    y: i32,
}

struct Person {
    name: String,
    age: u32,
}

// 自动生成 Debug 实现
impl_debug!(
    Point { x, y },
    Person { name, age }
);

fn main() {
    let p = Point { x: , y:  };
    println!("{:?}", p);  // Point { x: 1, y: 2 }
    
    let person = Person {
        name: "Alice".to_string(),
        age: ,
    };
    println!("{:?}", person);  // Person { name: "Alice", age: 30 }
}

🐛 常见坑点

坑点 1:宏的作用域

代码语言:javascript
复制
mod my_module {
    macro_rules! hello {
        () => { println!("Hello!"); };
    }
}

fn main() {
    hello!();  // ❌ 错误:宏不在作用域内
}

// ✅ 正确:导出宏
#[macro_export]
macro_rules! hello {
    () => { println!("Hello!"); };
}

坑点 2:宏展开顺序

代码语言:javascript
复制
macro_rules! add {
    ($a:expr, $b:expr) => {
        $a + $b
    };
}

fn main() {
    let x = ;
    // 宏在编译时展开,不是运行时
    let result = add!(x, );  // 展开为:x + 10
    println!("{}", result);  // 15
}

坑点 3:重复捕获变量的问题

代码语言:javascript
复制
macro_rules! bad_macro {
    ($x:expr) => {
        {
            let x = $x;  // 可能覆盖外部变量
            x + 
        }
    };
}

fn main() {
    let x = ;
    let result = bad_macro!();  // x 被覆盖了!
    // println!("{}", x);  // 编译错误
}

// ✅ 正确:使用唯一变量名
macro_rules! good_macro {
    ($x:expr) => {
        {
            let __macro_x = $x;
            __macro_x + 
        }
    };
}

坑点 4:宏调试困难

代码语言:javascript
复制
macro_rules! complex_macro {
    ($($tt:tt)*) => {
        // 一堆复杂的逻辑
        // 出错了都不知道哪有问题
    };
}

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

代码语言:javascript
复制
cargo install cargo-expand
cargo expand > expanded.rs

🎯 实战案例:日志宏

让我们实现一个实用的日志宏:

代码语言:javascript
复制
macro_rules! log {
    // 基础版本:只有消息
    ($msg:expr) => {
        {
            let now = std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .unwrap();
            println!("[{}] {}", now.as_secs(), $msg);
        }
    };
    
    // 带级别
    ($level:expr, $msg:expr) => {
        {
            let now = std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .unwrap();
            println!("[{}][{}] {}", now.as_secs(), $level, $msg);
        }
    };
    
    // 带格式化参数
    ($level:expr, $msg:expr, $($args:expr),+) => {
        {
            let now = std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .unwrap();
            println!("[{}][{}] {}", now.as_secs(), $level, format!($msg, $($args),+));
        }
    };
}

fn main() {
    log!("Application started");
    log!("INFO", "Processing request");
    log!("ERROR", "Failed to connect: {}", "timeout");
    
    // 输出:
    // [1709856000] Application started
    // [1709856000][INFO] Processing request
    // [1709856000][ERROR] Failed to connect: timeout
}

🧠 思维导图

37-宏编程(macro_rules!)
37-宏编程(macro_rules!)

📝 小结

  1. 宏在编译时展开,函数在运行时调用——这是核心区别
  2. macro_rules! 是声明式宏,用模式匹配定义展开规则
  3. 常用匹配器: :expr(表达式)、:ident(标识符)、:ty(类型)、:tt(token tree)
  4. 重复匹配是宏的超能力——(tt:tt),* 可以匹配任意数量的参数
  5. 宏可以创建 DSL,让代码更简洁、更有表现力
  6. 调试宏用 cargo expand,查看展开后的代码
  7. 宏有作用域,用 #[macro_export] 导出到 crate 根

金句时间:

宏就像代码打印机——你设计好模板,它帮你批量生产。

🔗 下篇预告

学完 macro_rules!,你可能觉得:"这已经够强大了吧?"

且慢!还有更厉害的 过程宏(Procedural Macros) 等着你:

  • 自定义 derive 宏(#[derive(MyTrait)]
  • 属性宏(#[my_attribute]
  • 函数宏(my_macro!() 的升级版)
  • 宏卫生(hygiene)
  • 调试宏的技巧

敬请期待 第 38 篇:高级宏

🔗 参考资料

  • The Rust Book - Macros
  • The Rust Reference - Macros
  • Macro Kitchen Sink
  • cargo-expand
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-06-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Larry的Hub 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 宏编程
    • 🎬 引入
    • 📌 核心概念:宏是什么?
      • 宏 vs 函数
      • 宏的两种类型
      • 宏的名字为什么有 !?
    • 💻 代码示例
      • 1. 第一个 macro_rules! 宏
      • 2. 带参数的宏
      • 3. 多个匹配规则
      • 4. 重复匹配(宏的真正威力)
      • 5. 实际案例:实现简化版的 vec!
      • 6. 创建 DSL:SQL 查询宏
      • 7. 实现 derive 宏的简化版
    • 🐛 常见坑点
      • 坑点 1:宏的作用域
      • 坑点 2:宏展开顺序
      • 坑点 3:重复捕获变量的问题
      • 坑点 4:宏调试困难
    • 🎯 实战案例:日志宏
    • 🧠 思维导图
    • 📝 小结
    • 🔗 下篇预告
    • 🔗 参考资料
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档