尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

nom 2.0 升级指南:从 nom 1.x 迁移的完整破坏性变更清单与修复方案

发布时间:2026/9/24 17:09:15

资讯中心
01
ARTICLE

nom 2.0 升级指南:从 nom 1.x 迁移的完整破坏性变更清单与修复方案

nom 2.0 升级指南:从 nom 1.x 迁移的完整破坏性变更清单与修复方案
nom 2.0 升级指南从 nom 1.x 迁移的完整破坏性变更清单与修复方案【免费下载链接】nomRust parser combinator framework项目地址: https://gitcode.com/gh_mirrors/no/nom导读本文基于 doc/archive/upgrading_to_nom_2.md 编写是 nom 1.x 用户迁移到 nom 2.0 的权威实操指南。nom 2.0 在引入大量新特性的同时对 1.x 中命名混乱的函数与宏、写法别扭的特性以及冗余功能进行了一次大清理因此带来了一批破坏性变更——不过其中大部分是无害的只需要机械地替换名称或调整写法即可。读完本文你将掌握如何通过 Cargo 特性开关适配新的错误系统、如何将eof函数替换为eof!()宏、如何处理基础解析器在空输入上返回Incomplete的新行为、如何迁移length_value!相关宏以及 endianness 参数如何从布尔值过渡到nom::Endianness枚举等全部变更点。背景说明当前仓库中的 nom 已演进到 8.0.0见 Cargo.toml本文记录的 2.0 变更属于历史升级路径中的关键一环。文中涉及的ErrorKind、Endianness、complete、Offset等概念至今仍是 nom 的核心设计结合当前源码可以更清晰地理解这些迁移背后的设计意图。Simple vs verbose errors错误系统被拆分为可选特性nom 1.0 的错误管理系统非常强大它允许你在解析树回溯backtracking时聚合错误并明确告诉你每个组合器处理了输入的哪一部分。但代价是性能为了在错误列表未被使用时将其丢弃编译器会生成大量额外代码拖慢了解析速度。并不是所有人都需要这一特性因此 nom 2.0 将它移到了一个名为verbose-errors的编译特性compilation feature之后。对于不使用Err枚举、不自定义错误码的项目升级到 2.0 后开箱即用即可直接编译通过并且在部分解析器上可以获得 30%50% 的性能提升该数据出自原文档。如果项目使用了旧错误系统编译时会看到类似下面的错误error: no associated item named Code found for type nom::ErrorKind_ in the current scope -- src/metadata/parser.rs:309:31 | 309 | _ IResult::Error(Err::Code( | ^^^^^^^^^ error: no associated item named Position found for type nom::ErrorKind_ in the current scope -- src/utility/macros.rs:16:41 | 16 | $crate::nom::IResult::Error($crate::nom::Err::Position( | ^^^^^^^^^^^^^^^^^^^^^^^^^^修复方式很简单在Cargo.toml中激活verbose-errors特性即可-nom ^1.0.0 nom { version ^2.0.0, features [verbose-errors] }如果项目只是用Err::Code来构造自定义错误码则可以考虑直接切到 simple errors2.0 用ErrorKindEu32类型直接替代了原来包含ErrorKindEu32的ErrInput, Eu32枚举也就是说错误码本身变成了错误类型不再需要外层枚举包装。从当前源码看这一演进方向被完整保留了下来如今 src/error.rs 中的ParseErrortrait 是错误系统的核心抽象所有解析器都泛型于实现了ParseErrorInput的错误类型而 src/error.rs 中的ErrorKind枚举Tag、Digit、LengthValue、Eof、Complete等则充当了统一错误码的角色。nom 还保留了(I, ErrorKind)元组错误类型和()空错误类型的 trait 实现src/error.rs以兼容早期代码——这与 2.0 simple errors 直接暴露ErrorKind 的迁移思路一脉相承。eof函数被移除替换为eof!()宏eof的原实现与输入类型耦合过深它需要了解输入的长度、是否为空等具体细节因此 2.0 将其改为宏组合器eof!()。如果你在升级时遇到以下错误error[E0432]: unresolved import nom::eof -- src/parser.rs:1:20 | 1 | use nom::{IResult, eof, line_ending, not_line_ending, space}; | ^^^ no eof in nom. Did you mean to use eol?修复方法是删除eof的导入并把所有eof调用替换为eof!()。-use nom::{IResult, eof, line_ending, not_line_ending, space}; use nom::{IResult, eol, line_ending, not_line_ending, space};有趣的是在当前版本中eof又以函数形式回归了见 src/combinator/mod.rs它检查input.input_len() 0输入为空时返回Ok((input, input))否则返回Err::Error且错误码为ErrorKind::Eof。这也印证了当年文档的判断——eof本质上只需要判断输入是否已耗尽完全可以做成语义清晰的独立组合器。基础解析器在空输入上改为返回Incompletealpha、digit、alphanumeric、hex_digit、oct_digit、space、multispace、sized_buffer这些基础解析器在 2.0 中遇到空输入时不再返回错误而是返回Incomplete。这一改动是为了让这些基础解析器行为更一致保持一致性的设计动机见原文档。如果你遇到下面的测试失败---- rules::literals::tests::case_invalid_hexadecimal_no_number stdout ---- thread rules::literals::tests::case_invalid_hexadecimal_no_number panicked at assertion failed: (left right) (left: Incomplete(Unknown), right: Error(Position(HexDigit, []))), source/rules/literals.rs:726可以用complete!组合器把Incomplete转换为Errornamed!(parse_hex[u8], [u8], complete!(hex_digit));这一流式/完整输入的语义区分在当今 nom 中依然是核心设计同一解析器存在 streaming 与 complete 两个版本。例如 src/character/streaming.rs 中的char在输入不足时返回Err::Incomplete(Needed::new(1))而 src/combinator/mod.rs 中的complete组合器则通过内部将模式切换为Complete把子解析器产生的Err::Incomplete(_)转换为Err::Error错误码为ErrorKind::Complete。如果你在升级 2.0 时用complete!包装了这些基础解析器这一迁移思路与 nom 后来按需选择 streaming/complete 版本的正式 API 是完全一致的。另外原文档特别提醒解析一个格式的基本元素例如某个 token 的字母表始终是高度定制化的这些通用函数未必总适合你的需求。这种情况下可以很容易地用take_while配合一个测试字符/字节的谓词函数来构造自己的解析器。take_till!改为按字节/字符迭代而非按它们的引用迭代2.0 要求输入类型符合新的 trait这带来了take_till!的行为变化谓词函数接收的参数从u8变成了u8。如果出现下面的类型不匹配错误error[E0308]: mismatched types -- src/linux/parser.rs:32:1 | 32 | named!(parse_c_string, take_till!(is_nul_byte)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected u8, found u8 | note: expected type u8 note: found type u8修复方法让谓词直接接收值而不是引用-fn is_nul_byte(c: u8) - bool { - *c 0x0 fn is_nul_byte(c: u8) - bool { c 0x0length_value!、length_bytes!重构length-value 模式通常表示先从输入中读取一个长度值再按该长度切出一段切片最后把这段切片转换成所需类型的值。在 nom 1.x 中length_value!宏用长度参数来控制 value parser 的执行次数——这个语义其实应该叫按长度计数与按长度取值并不相符。2.0 对此做了三处调整原length_value!宏被改名为length_count!保留按长度重复执行子解析器的语义新的length_value!宏先由第一个子解析器取得长度再切出该长度的切片然后在这段切片上应用第二个子解析器如果第二个解析器返回Incomplete整个解析失败新增length_data!从子解析器取得长度后直接返回该长度的子切片。典型迁移错误与修复如下error[E0308]: mismatched types -- src/tls.rs:378:37 | 378 | cert_types: cert_types, | ^^^^^^^^^^ expected struct std::vec::Vec, found u8 | note: expected type std::vec::Vecu8 note: found type u8fn parse_tls_handshake_msg_certificaterequest( i:[u8] ) - IResult[u8], TlsMessageHandshake { chain!(i, - cert_types: length_value!(be_u8,be_u8) ~ cert_types: length_count!(be_u8,be_u8) ~ sig_hash_algs_len: be_u16 ~在 TLS 握手消息这个例子中cert_types字段是一个1 字节长度 N 个 1 字节元素的数组需要的是按长度重复 N 次的语义因此改名为length_count!后行为完全正确。而如果你需要的是先读长度、再按长度取一段数据则对应新语义的length_value!或length_data!。这一命名上的纠偏让ErrorKind::LengthValue见 src/error.rs所代表的两种不同操作不再混为一谈。error!不复存在改名为return_error!1.x 中用于直接返回解析错误、不在解析树中回溯的error!宏在 2.0 中更名为return_error!。改名原因是log crate 也导出了一个error!宏而 log 的维护者选择向 nom 抱怨命名冲突原文档原话如此。同时add_error!宏也更名为add_return_error!。编译时可能出现的错误error: macro undefined: error! -- src/parser.rs:205:10 | 205 | error!(Custom(ParseError::InvalidData), | ^修复方式named!(repeatstr, u8, ParseError, - error!(Custom(ParseError::RepeatNotNumeric), fix!( return_error!(Custom(ParseError::RepeatNotNumeric), fix!( map_res!(flat_map!(take_s!(1), digit), FromStr::from_str))));注意示例中的用法return_error!的第一个参数是自定义错误Custom(ParseError::RepeatNotNumeric)这正是 nom 1.x/2.x 时期通过Err::Code(ErrorKind::Custom(...))携带自定义错误类型的典型写法。今天的 nom 已经把这一职责交给了ParseErrortrait 与ErrorKind::Custom见 src/error.rs 与 src/error.rs但显式返回自定义错误、跳过回溯的语义仍然存在。offset()方法移到OffsettraitHexDisplay只保留给字节切片nom 2.0 新增了Offsettrait并且为str也实现了该 trait而HexDisplaytrait 从此只保留给[u8]。在今天的源码中Offsettrait 依然位于 src/traits.rs其唯一方法fn offset(self, second: Self) - usize用于计算两个切片之间的字节偏移且要求第二个切片必须是第一个切片的一部分否则指针相减可能产生算术下溢。源码同时给出了[u8]的默认实现src/traits.rs通过指针相减计算偏移。如果你的代码依赖offset()方法升级时需要确保它来自nom::Offsettrait 的引入而不是旧版本的固有方法。AsChar::is_0_to_9改名为AsChar::is_dec_digit这是纯粹的一致性重命名AsChartrait 中判断十进制数字的方法从is_0_to_9改为is_dec_digit。迁移时只需全局替换方法名。可配置字节序的数字解析宏布尔参数改为Endianness枚举用布尔值表达字节序endianness容易引起混淆——true到底是大端还是小端因此 2.0 引入了nom::Endianness枚举数字解析宏的字节序参数从bool改为该枚举- named!(be_tst32u32, u32!(true)); - named!(le_tst32u32, u32!(false)); named!(be_tst32u32, u32!(Endianness::Big)); named!(le_tst32u32, u32!(Endianness::Little));这一设计一直沿用至今当前源码 src/number/mod.rs 中的Endianness枚举包含三个变体——Big大端、Little小端、Native与宿主机字节序一致后者是 2.0 时代没有的后续增强。而be_u8、be_u16、le_u32等固定字节序函数则是把这一枚举参数固化成了专用 API例如 src/number/mod.rs 中的be_u8。行结束符解析的统一在 1.x 中解析行结束符存在多种互不兼容的写法。2.0 统一了eol、line_ending和not_line_ending的行为先测试\n如果不对再测试\r\n。这修复了此前因换行符长度计算不一致导致的各类问题。// 现在 eol / line_ending 的行为一致 // 优先匹配 \n其次匹配 \r\n named!(parse_line_end, eol);迁移自检清单完成上述变更后可以用下面的清单快速核对升级是否彻底对照原文档的全部要点在Cargo.toml中确认已使用nom { version ^2.0.0, features [...] }并根据是否使用聚合错误决定是否启用verbose-errors全文搜索eof函数形式并替换为eof!()删除对应 import检查alpha/digit/space等基础解析器的调用点若依赖空输入即失败的语义用complete!包装take_till!的谓词签名从u8改为u8字符输入类推把按长度重复 N 次的length_value!改为length_count!按需改用新语义的length_value!或length_data!error!→return_error!add_error!→add_return_error!数字宏的字节序参数由true/false改为Endianness::Big/Endianness::Little若用到offset()确保通过Offsettrait 引入HexDisplay仅用于[u8]AsChar::is_0_to_9改为AsChar::is_dec_digit统一使用eol/line_ending解析行结束符先\n后\r\n。延伸阅读doc/archive/upgrading_to_nom_1.mdnom 1.0 升级指南可了解Err枚举泛型化、IResult生命周期消除、Incomplete行为调整等前置背景doc/archive/upgrading_to_nom_4.md 与 doc/archive/upgrading_to_nom_5.md后续大版本的迁移说明doc/error_management.md错误管理机制的完整介绍doc/choosing_a_combinator.md按场景选择合适的组合器src/error.rs当前ParseErrortrait 与ErrorKind枚举实现src/combinator/mod.rscomplete、eof等组合器的当前实现src/number/mod.rsEndianness枚举及数字解析函数examples/custom_error.rs自定义错误类型的完整示例。【免费下载链接】nomRust parser combinator framework项目地址: https://gitcode.com/gh_mirrors/no/nom创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。