news 2026/10/15 20:29:24

Rust RFC 0879 解读:二进制与八进制数字字面量的词法分析重构

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Rust RFC 0879 解读:二进制与八进制数字字面量的词法分析重构
  • 文档

【免费下载链接】rfcs

RFCs for changes to Rust

项目地址:https://gitcode.com/gh_mirrors/rf/rfcs
点击查看免费下载

导读

本文围绕 RFC 仓库中的 text/0879-small-base-lexing.md(RFC 879)展开,深入讲解 Rust 编译器词法层(lexer)对二进制(0b)与八进制(0o)数字字面量的词法规则设计:把这两种"小进制"字面量像十进制一样连续读取全部数字字符,而不是遇到非法数字就立刻截断切分 token。读完本文,你将理解为什么0b0123在宏展开中会静默产生"错误的结果"、新词法规则如何从根本上消除这类问题,以及该设计与 Rust 字面量后缀、保留前缀等周边词法机制之间的关联。

一、问题背景:Rust 词法层如何切分数字字面量

Rust 的源码处理分为词法(lexing/tokenizing)与语法(parsing)两个阶段。词法层负责把字符流切成 token,数字字面量则按四种进制分别识别。RFC 879 所针对的,正是词法层对二进制与八进制字面量"只认本进制合法数字"这一行为。

在同期文档 text/0090-lexical-syntax-simplification.md 中,作者用 ANTLR 文法完整描述了当时的词法语法,其中整数字面量的规则原文如下:

LIT_INTEGER : [0-9][0-9_]* INT_SUFFIX? | '0b' [01][01_]* INT_SUFFIX? | '0o' [0-7][0-7_]* INT_SUFFIX? | '0x' [0-9a-fA-F][0-9a-fA-F_]* INT_SUFFIX? ;

可以看到:二进制字面量的数字部分被限定为[01],八进制被限定为[0-7],十六进制则允许[0-9a-fA-F]。也就是说,词法器一遇到不属于该进制的数字(二进制中的 2–9、八进制中的 8–9)就会停止读取当前字面量,并立刻开始切分下一个 token。

二、动机:非法数字截断导致的两个实际问题

RFC 879 指出,上述"遇到非法数字即截断"的行为会带来两个层面的问题。

1. 普通代码中出现令人费解的错误信息

以0b0123为例:词法器读到0b01后遇到数字2——它不是合法的二进制数字,于是立即停止,把0b01当作一个 token,把紧随其后的23当作另一个 token。在普通表达式上下文里,两个 token 相邻是不合法的,于是编译器给出如下与问题本源毫无关联的报错:

<anon>:2:9: 2:11 error: expected one of `.`, `;`, `}`, or an operator, found `23` <anon>:2 0b0123 ^~

错误信息说的是"在23前面期望出现.、;、}或运算符",完全没有提示用户真正的错误在于0b0123中混入了非二进制数字2、3。显然,这与用户的实际意图(写出一个完整的二进制字面量)相去甚远。

2. 宏中可合法出现的"相邻 token"导致静默错误结果

普通代码中两个 token 直接相邻必然报错,但在macro_rules!中,使用tt(token tree,token 树)非终结符可以合法地捕获任意相邻的 token 序列。于是词法层的"截断切分"就会在宏展开时被放大成错误行为。

RFC 给出了一个极具说服力的示例:

macro_rules! expr { ($e: expr) => { $e } } macro_rules! add { ($($token: tt)*) => { 0 $(+ expr!($token))* } } fn main() { println!("{}", add!(0b0123)); }

这段代码的输出是24。展开过程是:0b0123在词法层被切成0b01与23两个 token,add!宏将其展开为0 + 0b01 + 23,即0 + 1 + 23 = 24。用户写下的0b0123本意是想表达某个二进制数(在修复后的语义下,它应当是一个词法错误),但程序却"正常"编译并打印出了 24——一个与用户意图完全无关、且没有任何警告的静默错误结果。

RFC 强调:普通代码中的问题或许还可以通过在词法器里特判该模式、定制专属错误信息来缓解,但宏场景无法用这种方式解决,因为tt捕获本身就允许 token 相邻存在。这正是必须从词法规则层面下手的原因。

三、详细设计:小进制字面量按"十进制字符集"连续词法化

1. 新词法语法

RFC 879 的核心改动非常简洁:词法层的字面量语法从"各进制只认自己的数字字符集"改为统一按十进制数字字符集[0-9]读取:

(0b[0-9]+ | 0o[0-9]+ | [0-9]+ | 0x[0-9a-fA-F]+) suffix

即二进制由原来的[01]放宽为[0-9],八进制由原来的[0-7]放宽为[0-9],十六进制保持[0-9a-fA-F]不变。整个数字字面量作为一个整体被读取,直到遇到非数字字符(或命中后缀)为止。

2. 错误仍然必须存在:非法数字是词法错误

放宽字符集并不意味着0b2这样的写法变得合法。RFC 明确规定:以0b或0o开头的数字字面量中若出现非法数字,在词法层就是错误(always an error in the lexer)。非法性从"token 切分点"转移为"整体字面量的内容校验",报错时机反而更清晰。

特别值得注意的是,RFC 用如下示例强调这种错误不因 token 未被使用而豁免:

macro_rules! ignore { ($($_t: tt)*) => { {} } } ignore!(0b0123)

即使宏ignore!匹配到 token 后将其完全丢弃、什么也不做,0b0123依然是一个词法错误。也就是说,非法数字的判定发生在词法层,早于宏展开与语义分析,任何宏都无法"吞掉"这类非法字面量。

3. 预期的错误信息形态

RFC 期望对两个场景(普通代码与宏)都给出明确指向的报错,其设想的信息格式如下:

error: found invalid digit `2` in binary literal 0b0123 ^

错误信息明确说出"在二进制字面量中发现非法数字2",并用插入符号精确指向出错的位置。相比旧的expected one of ...报错,这种信息能直接引导用户发现0b0123中混入了2、3。

4. 想要两个 token?请显式写出空格

对于确实希望把数字拆成两个 token 的代码,RFC 给出明确约定:用空格隔开即可,例如0b01 23。这既写法简单,也更能表达代码的真实意图——毕竟"相邻的两个数字 token"和"一个数字字面量"在语义上是截然不同的。

四、与相邻词法机制的关联:字面量后缀与保留前缀

数字字面量的词法行为并非孤立设计。RFC 879 的语法中末尾的suffix一词,指向 Rust 字面量后缀机制(1u8、0x10u32等)。理解周边文档有助于把握该设计的完整脉络。

1. 字面量后缀的"整体 token"化:RFC 463

在 text/0463-future-proof-literal-suffixes.md(RFC 463)中,Rust 将字面量后紧跟的标识符也并入同一 token(例如"foo"bar、1baz被视为一个整体 token,而不是"foo"与bar两个 token),目的同样是为未来扩展保留空间、避免宏中相邻 token 带来的歧义。该 RFC 的示例0b11u25即同时涉及小进制数字与后缀:

0b11u25 0x26i27 28.29e30f31

可见,小进制字面量与后缀机制共同构成了"数字字面量整体化"的演进方向:先保证数字部分按完整字符集读取(RFC 879),再保证后缀作为字面量的一部分被吞入(RFC 463),二者一前一后,把曾经模糊的 token 边界问题彻底消除。

2. 保留前缀:2021 edition 对词法空间的事先封锁

更晚的 text/3101-reserved_prefixes.md(RFC 3101)从 2021 edition 起保留了ident#foo、ident"foo"、ident'f'、ident#123这类语法形式,明确禁止未赋予含义的前缀直接附着在字面量上。该文档的动机部分特别提到"与字面量后缀机制的对称性"以及"为未来新增字面量前缀保留设计空间"。这与 RFC 879 共享同一个核心理念:词法层的 token 边界直接决定宏与未来语言扩展的可用空间,必须提前设计得保守而清晰。

五、代价与权衡:Rust 为何"与众不同"

RFC 879 坦率地列出了该设计的代价:

  • 它为 Rust 引入了一个略显特殊的规则——0b0123这种写法在 Rust 中是一个 token 且必报错,这在语法设计上是一个带有"独特个性"的特例。
  • 之所以大多数语言不必如此费心,是因为多数语言不直接暴露词法层给用户/宏系统。在那些语言里,0b1234到底是一个 token 还是两个 token 无法从外部区分,因为它无论如何都是错误。而 Rust 的macro_rules!允许以tt直接捕获 token 序列,词法边界因此成为可被用户观察、可被宏消费的语言行为,必须显式地、保守地定义。

这一权衡的深层逻辑是:词法边界的可观察性越高,语言设计者就越有义务让边界"无歧义"。Rust 选择了"非法数字仍整体成 token 并报错"这条路,从而保证任何宏都无法借助词法截断行为产生意外结果。

六、备选方案与未决问题

RFC 依次评估了如下替代设计:

  • 什么都不做(Don't do it):维持现状,但前述宏场景的静默错误就无法根除。
  • 把0b123理解为0b1加后缀23:即把紧跟的非法数字当作字面量后缀,是否报错取决于"后缀23是否合法"。RFC 指出,若要统一处理这种"字面量后紧跟内容"的情形,就需要把"foo"123、'a'123也一并词法化为单个 token——这与 RFC 463 的思路殊途同归,作者也承认"这可能本来就是个好主意"。

关于未决问题,RFC 879 给出的结论是None(无):设计在提出时已自洽,无需留待后续讨论。

七、总结:一条"限制语言"的词法规则

RFC 879 的头部元数据中有一句值得玩味的话——该特性的性质被标注为stable, it only restricts the language(稳定特性,它只是限制语言)。这句话精准概括了这次改动的本质:它没有引入任何新语法能力,而是收紧了合法程序的集合,把"曾经合法(但结果荒谬)"的宏写法变成"明确报错"的非法写法,用一次有限的破坏换取长期的可预期性。

最终形成的词法约定可以浓缩为三条:

  1. 0b/0o开头的字面量按[0-9]完整读取,非法数字不再充当 token 切分点;
  2. 非法数字是词法级错误,任何宏(哪怕直接丢弃 token)都无法绕过;
  3. 需要多个 token 的写法请显式写空格(如0b01 23),意图更清晰。

对于 Rust 编译器开发者与宏作者而言,这条规则意味着:词法边界是宏语义的基石,设计时宁可保守报错,也不要让截断行为悄悄改写程序含义。

  • 文档

【免费下载链接】rfcs

RFCs for changes to Rust

项目地址:https://gitcode.com/gh_mirrors/rf/rfcs
点击查看免费下载
上一篇:LMFlow NLL 自动评测框架详解:LLM 对话、常识与指令跟随能力的低成本 Benchmark 实践
下一篇:Celery 并发执行池:使用 gevent 实现高并发任务处理实战指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 20:25:22

JMeter 5.6.2压力测试实战:从线程组到报告解读

简介&#xff1a;Apache JMeter 5.6.2 是由 Apache 组织维护的开源纯 Java 压力测试工具&#xff0c;具备跨平台特性&#xff0c;可运行于 Windows、Linux、macOS。其用途在于模拟大量用户并发访问&#xff0c;评估 Web 应用、数据库、FTP 服务器等静态与动态资源的高并发稳定性…

作者头像 李华
网站建设 2026/10/15 20:23:36

Path of Building 完整指南:3 分钟规划好你的流放之路 Build

Path of Building 完整指南&#xff1a;3 分钟规划好你的流放之路 Build 【免费下载链接】PathOfBuilding Offline build planner for Path of Exile. 项目地址: https://gitcode.com/GitHub_Trending/pa/PathOfBuilding 还在凭感觉估算一件装备、一个天赋点到底带来多少…

作者头像 李华
网站建设 2026/10/15 20:22:41

Faster R-CNN源码解析:从RPN到RoI Head的PyTorch实现细节

最近重看了一遍 simple-faster-rcnn-pytorch 的源码&#xff0c;好几处当年囫囵吞枣跳过去的细节&#xff0c;这次总算想明白了。这个库可太适合拿来啃了——它没有把网络结构塞进一个几千行的 mega 文件里&#xff0c;RPN、RoI Head、Proposal 筛选这些都是单独模块&#xff0…

作者头像 李华
网站建设 2026/10/15 20:21:55

大麦网自动抢票脚本:6 个参数 + 4 步部署,开售瞬间自动下单

大麦网自动抢票脚本&#xff1a;6 个参数 4 步部署&#xff0c;开售瞬间自动下单 【免费下载链接】Automatic_ticket_purchase 大麦网抢票脚本 项目地址: https://gitcode.com/GitHub_Trending/au/Automatic_ticket_purchase Automatic_ticket_purchase 是一个大麦网自…

作者头像 李华