当前位置: 首页 > news >正文

【Rust】18-宏系统:声明宏、过程宏与代码生成

宏系统:声明宏、过程宏与代码生成

研究目标

  • 理解 Rust 宏在编译期生成代码的角色。
  • 区分声明宏和过程宏。
  • 掌握宏适合解决的问题和不适合滥用的边界。

为什么需要宏

Rust 是静态类型语言,很多重复模式在运行时抽象之前就已经显得冗长。宏可以在编译期生成代码,用于:

  • 减少重复样板。
  • 创建小型 DSL。
  • 根据语法结构生成实现。
  • 实现属性驱动的框架集成。

标准库中的println!vec!format!都是宏。

fnmain(){letvalues=vec![1,2,3];println!("{values:?}");}

宏调用带!,说明它不是普通函数调用。

声明宏 macro_rules!

声明宏通过模式匹配输入 token 并展开输出 token:

macro_rules!say_hello{()=>{println!("hello");};}fnmain(){say_hello!();}

带参数的例子:

macro_rules!make_vec{($($item:expr),*$(,)?)=>{{letmutvalues=Vec::new();$(values.push($item);)*values}};}fnmain(){letvalues=make_vec![1,2,3];println!("{values:?}");}

$item:expr表示匹配表达式,$()*表示重复匹配。

片段说明符

macro_rules!常见片段类型包括:

  • expr:表达式。
  • ident:标识符。
  • ty:类型。
  • path:路径。
  • pat:模式。
  • stmt:语句。
  • block:代码块。
  • tt:token tree。

选择合适片段能让宏输入更结构化,也能得到更好的错误信息。

宏卫生性

Rust 宏具有一定卫生性,宏内部定义的局部变量通常不会意外捕获调用处变量:

macro_rules!demo{()=>{{letvalue=1;value}};}fnmain(){letvalue=10;println!("{}",demo!());println!("{value}");}

但宏仍然可能引入难读代码、复杂错误和路径解析问题。编写导出宏时,常用$crate引用当前 crate:

#[macro_export]macro_rules!call_helper{()=>{$crate::helper()};}

过程宏

过程宏接收 token stream,输出 token stream。它们本质上是编译期运行的 Rust 函数。

过程宏分三类:

  • 派生宏:#[derive(MyTrait)]
  • 属性宏:#[my_attribute]
  • 函数式宏:my_macro!(...)

过程宏通常放在独立 crate 中,并配置:

[lib] proc-macro = true

派生宏

派生宏用于根据结构体或枚举定义生成 trait 实现:

#[derive(Debug, Clone)]structUser{name:String,}

常见第三方例子包括serde_derivethiserrorclap等。它们读取类型结构,生成序列化、错误实现或 CLI 解析代码。

一个概念化的派生宏流程:

input tokens: struct User { name: String } parse into syntax tree inspect fields generate impl MyTrait for User return output tokens

实际开发通常使用syn解析输入,用quote生成输出。

属性宏

属性宏可以改写或包裹一个 item:

#[route(GET,"/users")]fnusers(){}

Web 框架常用属性宏把函数注册为路由。测试框架、异步运行时、序列化框架也大量使用属性宏。

属性宏能力很强,但也可能隐藏控制流。使用时应确保团队能理解宏展开后的行为。

函数式过程宏

函数式过程宏看起来像声明宏调用,但内部由 Rust 代码处理 token:

sql!(SELECT*FROMusersWHEREid=1);

它适合实现复杂 DSL、编译期校验、生成类型安全接口等。与macro_rules!相比,过程宏更灵活,但编写和维护成本更高。

宏展开与调试

宏问题常见调试方法:

  • 使用cargo expand查看展开代码。
  • 给宏生成代码保留清晰路径。
  • 避免一次生成过大、过深的代码。
  • 在过程宏中提供精确 span 和错误信息。

示例:

cargoinstallcargo-expandcargoexpand

展开后的代码不一定适合阅读全部细节,但能帮助定位实际生成了什么。

何时使用宏

适合使用宏:

  • 普通函数和泛型无法表达的语法抽象。
  • 需要生成大量结构相似代码。
  • 需要读取类型定义并生成实现。
  • 框架入口确实需要属性式声明。

不适合使用宏:

  • 只是为了少写几行普通函数。
  • 业务逻辑复杂但可以用类型和函数表达。
  • 团队难以维护的 DSL。
  • 会显著恶化编译错误的场景。

常见误解

  • 宏不是运行时反射;它发生在编译期。
  • macro_rules!不是字符串替换,而是 token 模式匹配。
  • 过程宏能力强,但不是免费抽象,会增加编译复杂度。
  • 宏生成代码也必须满足所有权、借用和类型规则。

继续研究

  • Rust Reference:macros by example、procedural macros。
  • The Little Book of Rust Macros。
  • synquoteproc-macro2文档。
  • cargo expand用于观察宏展开。

后记

2026年6月11日15点27分于上海。

http://www.jsqmd.com/news/997612/

相关文章:

  • 2026年长春小提琴培训行业观察:教学体系、师资结构与学员成长路径分析 - 优质品牌商家
  • 2026深圳黄金回收便民服务指南,规范门店名录与特色优势全览! - 奢侈品交易观察员
  • 2026 湖州厨卫屋面地下室漏水瓷砖空鼓测评:吉修匠 99.8 分五星榜首 - 吉修匠
  • Windows下可直接运行的模板旋转匹配工具:自动输出XY坐标和旋转角度
  • Windows右键菜单终极清理指南:一键告别臃肿菜单的完整教程
  • Hugging Face Transformers:从模型加载到边缘部署的工业级AI工作流
  • 从《宫娥》到《睡莲》:技术博主如何用图像学方法看懂艺术史里的“密码”?
  • 从汽车级EEPROM选型到开源磨损均衡算法:手把手教你设计高可靠嵌入式存储模块(附避坑指南)
  • 伪Anosov流与双曲3-流形构造技术解析
  • 深入MAX30102算法核心:手把手解读心率血氧计算函数,告别‘黑盒’调用
  • 别再死记硬背了!用Python 3.10手把手模拟TDM时分复用,5分钟搞懂同步与异步
  • 从Betaflight到Ardupilot:为什么你的AT32飞控板还跑不了?聊聊ChibiOS移植的那些坑
  • 拼多多代运营公司怎么样?拼多多代运营公司手福音,保姆式托管 + 全流程代操作(附联系方式) - 百推信源
  • 从EMV到物联网:TLV编码的前世今生与实战避坑指南
  • Python 高手编程系列三千四百四十三:setup.cfg
  • 从玩具车到真汽车:聊聊EEPROM磨损均衡算法在Arduino和STM32上的开源实现
  • 如何用ImageSearch在5分钟内实现本地图像搜索:千万级图片库管理终极指南
  • FPGA入门指南----从可编程逻辑到片上系统
  • Rust + GPU加速?拆解Zed编辑器‘快’背后的技术栈与未来潜力
  • 深入S32K3xx的‘五脏六腑’:手把手配置TCM、Cache与内存保护(XRDC/MPU),让代码飞起来
  • 从V1到V3:MobileNet家族进化史,看谷歌如何用‘倒残差’和SE模块把模型越做越小
  • 2026 肇庆防水补漏服务商口碑测评榜单|全屋渗漏维修机构优选指南 - 宅安选房屋修缮
  • 3个步骤,让计算机学会“审美“:AI图像质量评估实战指南
  • 知识图谱与图嵌入在分布式决策系统中的应用
  • Autosar DSL模块实战:如何用Vector Configurator Pro精准控制诊断时序与Pending响应?
  • Python 高手编程系列三千四百四十二:创建一个包
  • JetBrains IDE试用延期解决方案:ide-eval-resetter完整指南
  • 扩散模型在视频生成中的手部与相机控制技术
  • 百度网盘解析工具终极指南:快速获取真实下载地址,告别龟速下载
  • 别再只看CPU核数了!手把手教你用FLOPS公式,自己算算你的电脑和显卡到底有多强