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

用逆波兰表达式,彻底搞懂 Rust 宏的递归写法

原文Writing complex macros in Rust: Reverse Polish Notation作者 Ingvar StepanyanCloudflare Blog。Rust 的宏系统功能强大但也以难以掌握著称。很多人读完官方文档、照着示例写了几个简单的宏之后一旦遇到需要处理复杂 token 序列的场景就完全不知道从何下手了。这篇文章以 Cloudflare 工程师的一篇技术博客为蓝本通过实现一个编译期的逆波兰表达式求值宏把 Rust 声明宏macro_rules!的核心技巧完整地走一遍。什么是逆波兰表达式逆波兰表达式Reverse Polish NotationRPN也叫后缀表达式是一种不需要括号就能表达运算优先级的记法。它依赖一个栈来工作遇到操作数压栈遇到运算符从栈中取出两个操作数计算结果后再压回栈举个例子RPN 表达式2 3 4 *执行步骤如下2入栈 → 栈[2]3入栈 → 栈[3, 2]遇到取出3和2计算2 3 5压回 → 栈[5]4入栈 → 栈[4, 5]遇到*取出4和5计算5 * 4 20压回 → 栈[20]表达式结束栈顶即为结果20对应的中缀表达式是(2 3) * 4。我们的目标是写一个宏让下面的代码能在编译期完成求值println!({},rpn!(234*));// 20第一步用 token 序列模拟栈Rust 宏没有变量这个概念无法在运行时维护一个真正的栈。但宏可以在递归调用时携带一段 token 序列用来充当编译期的栈。我们用方括号包裹的、逗号分隔的expr序列来表示栈[$($stack:expr),*]每次递归调用时我们把这个栈更新后传给下一次调用以此来模拟栈的 push/pop 操作。第二步处理操作数先写处理单个数字操作数的分支。把数字压入栈然后继续处理剩余 tokenmacro_rules!rpn{([$($stack:expr),*]$num:tt$($rest:tt)*){rpn!([$num$(,$stack)*]$($rest)*)};}这里有两个关键点为什么用tt而不是expr或literal因为expr会贪婪地匹配可能把2 3整体吃掉而我们只需要匹配一个 token。tttoken tree恰好只匹配一个 token 树。递归是宏处理序列的唯一方式。宏不能用循环也不能修改变量。通过递归每次把处理好的新栈状态传入下一次调用直到消耗完所有 token这是声明宏处理列表的标准模式。第三步处理运算符运算符分支需要从栈中弹出两个操作数组合成中缀表达式后压回macro_rules!rpn{([$b:expr,$a:expr$(,$stack:expr)*]$($rest:tt)*){rpn!([$a$b$(,$stack)*]$($rest)*)};// - * / 类似...}注意栈中元素的顺序先入栈的$a在后后入栈的$b在前因为栈顶在左侧。运算时是$a op $b而不是$b op $a减法和除法的情况下这一点尤为重要。由于四个运算符的处理逻辑完全相同重复写四次显然不够优雅。第四步用op内部 helper 消除重复Rust 宏不能调用外部 helper但可以在同一个宏里定义内部分支用一个特殊的标记 token如op作为标识符与正常输入区分开macro_rules!rpn{// 内部 helper执行实际运算(op[$b:expr,$a:expr$(,$stack:expr)*]$op:tt$($rest:tt)*){rpn!([$a$op$b$(,$stack)*]$($rest)*)};// 四个运算符统一转发给 op($stack:tt$($rest:tt)*){rpn!(op$stack$($rest)*)};($stack:tt-$($rest:tt)*){rpn!(op$stack-$($rest)*)};($stack:tt*$($rest:tt)*){rpn!(op$stack*$($rest)*)};($stack:tt/$($rest:tt)*){rpn!(op$stack/$($rest)*)};// 操作数压栈([$($stack:expr),*]$num:tt$($rest:tt)*){rpn!([$num$(,$stack)*]$($rest)*)};}这里还有一个技巧在运算符分支里整个栈$stack被作为tt整体传递因为它是一个被方括号包裹的 token 树不需要展开里面的内容。只有在op分支里才真正拆解栈的内部结构。第五步处理终止条件和入口当所有 token 处理完毕栈中应该剩下唯一的结果([$result:expr]){$result};还需要一个入口分支让调用者不必手动传入空栈[]($($tokens:tt)*){rpn!([]$($tokens)*)};注意分支顺序很重要。这个兜底分支必须放在最后否则它会匹配一切导致其他分支永远无法触发。完整宏定义如下macro_rules!rpn{(op[$b:expr,$a:expr$(,$stack:expr)*]$op:tt$($rest:tt)*){rpn!([$a$op$b$(,$stack)*]$($rest)*)};($stack:tt$($rest:tt)*){rpn!(op$stack$($rest)*)};($stack:tt-$($rest:tt)*){rpn!(op$stack-$($rest)*)};($stack:tt*$($rest:tt)*){rpn!(op$stack*$($rest)*)};($stack:tt/$($rest:tt)*){rpn!(op$stack/$($rest)*)};([$($stack:expr),*]$num:tt$($rest:tt)*){rpn!([$num$(,$stack)*]$($rest)*)};([$result:expr]){$result};($($tokens:tt)*){rpn!([]$($tokens)*)};}测试println!({},rpn!(234*));// 20println!({},rpn!(15711-/3*211-));// 5两行都能正确输出且完全在编译期求值。第六步让错误信息更有用一个生产可用的宏还需要处理非法输入时给出清晰的错误提示而不是让编译器抛出莫名其妙的类型错误。情况一操作数过多缺少运算符输入rpn!(2 3 7 4 *)时栈最终有两个值而不是一个。此时会触发兜底分支产生难以理解的类型错误。解决方案在终止分支和兜底分支之间插入一个匹配栈里有多个值的错误分支([$($stack:expr),*]){compile_error!(concat!(表达式求值失败可能缺少运算符。当前栈状态,stringify!([$($stack),*])))};情况二操作数不足缺少操作数输入rpn!(2 3 *)时栈只有一个值却遇到了运算符op分支无法匹配两个操作数导致字符被当成普通 token 压栈产生奇怪的错误。解决方案给op也加一个兜底错误分支(op$stack:tt$op:tt$($rest:tt)*){compile_error!(concat!(运算符 ,stringify!($op), 无法应用于当前栈,stringify!($stack)))};加入这两个分支后错误信息会清晰地告诉用户问题所在error: 运算符 * 无法应用于当前栈[ 2 3 ]调试技巧trace_macros!宏的递归展开过程很难在脑子里完整跟踪。Rust nightly 提供了trace_macros!宏可以打印出每一步的展开过程#![feature(trace_macros)]fnmain(){trace_macros!(true);leterpn!(234*);trace_macros!(false);println!({},e);}编译时会输出类似这样的展开链expanding rpn! { 2 3 4 * } to rpn ! ( [ ] 2 3 4 * ) expanding rpn! { [ ] 2 3 4 * } to rpn ! ( [ 2 ] 3 4 * ) ...写复杂宏时这是定位问题最直接的工具。总结Rust 声明宏的三个核心技巧通过这个例子可以总结出编写复杂macro_rules!宏的三个核心模式1. 用 token 序列模拟状态宏没有变量但可以把状态编码在一段 token 序列里随着递归调用一路传下去。数据结构、栈、累加器都可以用这种方式实现。2. 用标记划分内部 helper在同一个宏里用特殊前缀如op、parse标记内部分支实现逻辑分层和代码复用避免大量重复的分支。3. 分支顺序决定匹配优先级macro_rules!按分支定义顺序逐一尝试匹配更具体的分支要放在更通用的分支之前。兜底的$($tokens:tt)*必须永远在最后。这三个技巧组合在一起足以应对绝大多数需要在编译期处理复杂 token 序列的场景。

相关文章:

用逆波兰表达式,彻底搞懂 Rust 宏的递归写法

原文:Writing complex macros in Rust: Reverse Polish Notation,作者 Ingvar Stepanyan,Cloudflare Blog。 Rust 的宏系统功能强大,但也以"难以掌握"著称。很多人读完官方文档、照着示例写了几个简单的宏之后&#xf…...

D2RML:暗黑破坏神2重制版多开神器,让你告别繁琐登录的终极解决方案

D2RML:暗黑破坏神2重制版多开神器,让你告别繁琐登录的终极解决方案 【免费下载链接】D2RML Diablo 2 Resurrected Multilauncher 项目地址: https://gitcode.com/gh_mirrors/d2/D2RML 还在为切换暗黑2重制版账户而烦恼吗?每次登录战网…...

5步打造你的专属AI角色:SillyTavern让对话不再单调

5步打造你的专属AI角色:SillyTavern让对话不再单调 【免费下载链接】SillyTavern LLM Frontend for Power Users. 项目地址: https://gitcode.com/GitHub_Trending/si/SillyTavern 厌倦了千篇一律的AI对话?想要创造有灵魂、有个性的虚拟伙伴吗&am…...

题解:洛谷 P2540 [NOIP 2015 提高组] 斗地主 加强版

本文分享的必刷题目是从蓝桥云课、洛谷、AcWing等知名刷题平台精心挑选而来,并结合各平台提供的算法标签和难度等级进行了系统分类。题目涵盖了从基础到进阶的多种算法和数据结构,旨在为不同阶段的编程学习者提供一条清晰、平稳的学习提升路径。 欢迎大…...

AI大模型学习指南:小白也能掌握的AI核心技能,收藏这份干货!

本文深入浅出地介绍了AI的概念、核心目标及四大研究领域,包括基础设施建设、算法研发、主要技术方向和行业解决方案。文章详细阐述了各领域代表公司及优质岗位,并特别针对算法岗位的学习路径进行了指导,帮助读者了解AI技术全貌,为…...

0.4 阅读本专栏的前置知识与环境搭建指南

摘要: 本篇从"知识准备"和"环境搭建"两个维度,帮助读者评估自身的阅读准备度,并搭建一个可编译、可调试、可运行 libhsakmt 的本地环境,为后续各章的源码级分析打下基础。 1. 前置知识体系 阅读本专栏不需要…...

播丫科技AI数字人直播:赋能实体商家,解锁线上引流新密码

播丫科技AI数字人直播:赋能实体商家,解锁线上引流新密码实体生意竞争加剧,线上增量已成为实体店生存发展的必争之地。购物中心、工厂等实体业态虽有线下实景优势,却普遍面临“想做直播却请不起主播、不会运营”的困境,…...

异步编程的发展

线程的终结 早年写服务端,逻辑很简单:一个请求一个线程。 用户 A 请求 → 创建线程 A → 查数据库 → 返回结果 用户 B 请求 → 创建线程 B → 查数据库 → 返回结果代码写起来像同步程序一样自然——因为它本来就是同步的。你不需要关心什么异步、回调、…...

GPT-5.5发布解读,从Benchmark到Agent执行能力看它强在哪

GPT-5.5 发布之后,很多开发者第一反应是两句:“又贵了”和“好像真强了”。如果只看这两个结论,其实不够。 从技术视角看,GPT-5.5 这次真正值得拆的,不只是模型分数,而是它在 Agent 执行能力上的变化。Open…...

Qwen-Image 从推理到 LoRA 训练实战教程(AMD GPU × DiffSynth-Studio)

Qwen-Image 从推理到 LoRA 训练实战教程(AMD GPU DiffSynth-Studio) 原文作者:段忠杰,魔搭社区 本课程由魔搭社区 ModelScope 出品,通过实战教程深入讲解如何在 AMD GPU 环境下,结合开源框架DiffSynth-Stu…...

AMD Hummingbird-XT: 面向消费端的高性能视频生成算法

AMD Hummingbird-XT: 面向消费端的高性能视频生成算法 原文作者:Takashi Isobe, He Cui, Mengmeng Ge, Dong Zhou, Dong Li, KuanTing Lin, Chandra Yang, Wickey Wang, Emad Barsoum. 引言 随着近些年扩散模型的出现与快速发展[1],视频生成算法在分辨…...

福利|110万美元奖金池!AMD E2E SpeedRun火力全开,等你刷新推理榜单!

福利|110万美元奖金池!AMD E2E SpeedRun火力全开,等你刷新推理榜单! 原文作者:George Wang, Daniel Huang, Guru Madagundapaly Parthasarathy, AI Group我们宣布正式启动由AMD 赞助的百万美金奖金池「GPU MODE E2E Sp…...

Unity UI粒子特效完整解决方案:高效实现专业级视觉效果

Unity UI粒子特效完整解决方案:高效实现专业级视觉效果 【免费下载链接】ParticleEffectForUGUI Render particle effect in UnityUI(uGUI). Maskable, sortable, and no extra Camera/RenderTexture/Canvas. 项目地址: https://gitcode.com/gh_mirrors/pa/Partic…...

三步快速对接 gpt-image-2 图像生成 API 教程

前言 gpt-image-2 是当下高性能 AI 图像生成模型,支持自定义尺寸、风格定制、批量生成等能力,接口规范完全标准化、兼容主流开发生态。 本文基于官方 Apifox 接口文档,以 https://api.aaigc.top 为统一请求域名,整理极简三步对接…...

InkOS:基于多Agent协作与长期记忆的AI小说创作系统深度解析

1. 项目概述:一个能自主写小说的AI Agent如果你对AI写作的印象还停留在“输入一句话,生成一段文”的简单工具,那么InkOS可能会颠覆你的认知。这不是一个玩具,而是一个拥有完整创作管线、具备长期记忆和自主审计能力的“小说创作AI…...

终极指南:Switch大气层系统1.7.1完整安装与功能解锁

终极指南:Switch大气层系统1.7.1完整安装与功能解锁 【免费下载链接】Atmosphere-stable 大气层整合包系统稳定版 项目地址: https://gitcode.com/gh_mirrors/at/Atmosphere-stable 想要为你的Nintendo Switch解锁更多可能性吗?大气层&#xff08…...

Ubuntu 22.04 系统上完整安装 ROS 2 Humble

第一步:确保系统支持 UTF-8 编码sudo apt update && sudo apt install locales sudo locale-gen en_US en_US.UTF-8 sudo update-locale LC_ALLen_US.UTF-8 LANGen_US.UTF-8 export LANGen_US.UTF-8第二步:添加 ROS 2 软件源# 安装 curl sudo ap…...

Botty:暗黑2重制版自动化助手,解放双手的智能刷宝方案

Botty:暗黑2重制版自动化助手,解放双手的智能刷宝方案 【免费下载链接】botty D2R Pixel Bot 项目地址: https://gitcode.com/gh_mirrors/bo/botty 还在为暗黑2重制版中重复枯燥的刷怪、捡装备而烦恼吗?Botty这款开源自动化工具正是你…...

Klipper共振补偿:彻底解决3D打印“幽灵纹路“的专业指南

Klipper共振补偿:彻底解决3D打印"幽灵纹路"的专业指南 【免费下载链接】klipper Klipper is a 3d-printer firmware 项目地址: https://gitcode.com/GitHub_Trending/kl/klipper Klipper共振补偿技术是消除3D打印中"幽灵纹路"&#xff0…...

【Kubernetes专项】温故而知新,重温技术原理(1)

1.简单说说什么是kubernetes? K8s 是一个开源的容器编排平台;用来自动调度、弹性扩缩容、自愈修复及管理容器化应用核心作用:统一管理容器生命周期,简化大规模容器集群的部署、运维及管理的难题。2.Pod是什么?和容器有什么区别? …...

慧科讯业:2026年北京车展前瞻报告

行业背景政策:汽车政策从补贴转向内需 技术双轮驱动,L3 自动驾驶准入标准 2026 年落地,新能源车购置税减半至 2027 年。消费:购车群体年轻化,26-35 岁占比 42.3%,智能化成核心标配,决策更理性。…...

从零到一:Nessus 实战部署与合规性扫描指南

1. Nessus 基础认知与企业级部署准备 第一次接触Nessus的企业安全团队,往往会被其复杂的配置界面吓退。其实这个号称"漏洞扫描界的瑞士军刀"的工具,本质上就是个会主动敲门的安全检查员。想象一下,你新接手一栋商业大厦的安保工作…...

从‘超能力者大赛’到图论建模:如何用Floyd算法解决天梯赛L3-034的路径规划问题

从‘超能力者大赛’到图论建模:如何用Floyd算法解决天梯赛L3-034的路径规划问题 在算法竞赛中,题目往往通过精心设计的故事情节来包装核心算法问题。这类题目考验的不仅是编码能力,更是快速识别问题本质的洞察力。L3-034"超能力者大赛&q…...

iOS与tvOS非越狱自定义工具Misaka深度解析与实战指南

iOS与tvOS非越狱自定义工具Misaka深度解析与实战指南 【免费下载链接】misaka iOS & tvOS customisation tool for KFD & MDC 项目地址: https://gitcode.com/gh_mirrors/mis/misaka Misaka是一款面向iOS和tvOS设备的革命性自定义工具,它通过KFD和M…...

以太网端口的ESD防护器件选型

ESD是以太网端口最常见的失效诱因,防护器件的选型直接影响端口可靠性和信号完整性。TVS管是首选防护器件,响应速度快(ps级),钳位电压低。关键参数包括:工作电压(VRWM)需高于信号峰值…...

real-anime-z创意拓展:结合‘雨景’‘霓虹’‘樱花’等氛围词激发新构图

real-anime-z创意拓展:结合雨景霓虹樱花等氛围词激发新构图 1. 动漫风格创作新思路 在动漫创作中,氛围感的营造往往能让作品脱颖而出。real-anime-z作为专业的二次元文生图工具,特别擅长通过氛围词来激发创意构图。本文将重点展示如何利用&…...

基于SSH的多跳远程访问工具PKURemote:原理、实现与配置管理

1. 项目概述与核心价值最近在折腾远程办公和实验室资源访问时,发现了一个挺有意思的项目,叫“PKURemote”。光看名字,你大概能猜到它和高校有关,没错,这最初是围绕特定学术机构内网环境访问需求而诞生的一个工具集。但…...

ChanlunX缠论插件:通达信上的终极缠论分析神器

ChanlunX缠论插件:通达信上的终极缠论分析神器 【免费下载链接】ChanlunX 缠中说禅炒股缠论可视化插件 项目地址: https://gitcode.com/gh_mirrors/ch/ChanlunX 你是否在通达信软件中苦苦寻找高效的缠论分析工具?是否厌倦了手动绘制笔段和中枢的繁…...

AI Agent Harness Engineering 的安全性挑战:提示词注入与越狱

AI Agent Harness Engineering 的安全性挑战:提示词注入与越狱 3-5个标题备选 《从LangChain构建的AI Agent到企业内网泄密:提示词注入与越狱的完整攻防手册》 《AI Agent Harness实战避坑:5分钟带你理解为何90%的初级Agent存在致命安全漏洞》 《告别“裸奔”的AI助手:Pro…...

如何快速搭建个人AI助手?Open WebUI完整指南让你轻松掌控本地AI

如何快速搭建个人AI助手?Open WebUI完整指南让你轻松掌控本地AI 【免费下载链接】open-webui User-friendly AI Interface (Supports Ollama, OpenAI API, ...) 项目地址: https://gitcode.com/GitHub_Trending/op/open-webui 想象一下,你正在处理…...