Rust 中的“解析而非验证”模式

Rusty thoughts on "Parse, don't validate"

Alexis King 提出的“解析而非验证”理念在 Rust 中有着独特的实践方式。文章探讨了如何利用 Rust 的类型系统,将运行时验证转化为编译期保证。通过引入 NonEmpty 类型、AbsPathBuf 以及 NonZero 等概念,开发者可以在数据解析阶段就确立不变量,从而消除后续代码中对空值或无效状态的冗余检查。这种从 Vec 到 NonEmpty、从 PathBuf 到 AbsPathBuf 的类型转换,不仅提升了代码的清晰度,还避免了潜在的运行时错误,真正实现了“解析即验证”的优雅设计。

一旦解析器将 mode 转换为 Mode 枚举,就不再需要任何进一步的验证。
  1. articulatepang

    我更喜欢一条稍微更通用的原则:让非法状态无法表示(Make Illegal States Unrepresentable,简称 MISU)。“解析而非验证”原则只是 MISU 的一个特例。

    两者的区别在于:即使没有发生类似解析的转换,MISU 依然适用。例如,假设你有一个变量表示网络连接的状态,它可以是 Disconnected(断开),或者 Connected to some IP address(连接到某个 IP 地址)(这里做了简化)。

    那么一种实现方式可能是:

    struct {

    connected: bool,

    peer_ip: int32

    }

    问题在于,这允许我们表示一种非法或无意义的状态:虽然已断开连接,但 peer_ip 字段里还残留着一些旧的垃圾数据。更糟糕的是,我们可能会写成:

    struct {

    connected: bool,

    peer_ip: Option<int32>

    }

    这样就会出现 connected = true 但 peer_ip = None 的情况。

    解决方案是使用和类型(sum type):

    type connection =

    Disconnected

    | Connected of int32

    (抱歉使用了虚构的语法;希望熟悉 Rust 的人能看懂。)

    “让非法状态无法表示”这一原则适用于程序的各个层面,包括模块或函数之间的每一个接口,解析输入只是其中之一。

  2. Fluorescence

    我不确定这种类型是否真是个好建议:

    pub struct NonEmpty<T> {

    pub head: T,

    pub tail: Vec<T>,

    }

    你不得不手动实现各种 trait 来支持切片和迭代的易用性,而且如果需要以 Vec 的形式传递所有权,还会付出昂贵的重新分配代价。

    我预期的写法是:

    pub struct NonEmpty<T> {

    v: Vec<T>,

    }

    构造函数负责强制约束不变式,然后为 [T] 实现 Deref 和 DerefMut,从而获得正常的 len/is_empty/索引/迭代功能,可以以 &[T] 的形式传递给其他函数,也可以修改值(这不会破坏不变式)。

    至于如何在保持不变式的前提下修改长度,则看开发者自己的选择了,例如:

    - 添加 .into_vec() 方法用于解包/修改/重新包装

    - 添加你自定义的保持不变式的修改器

  3. Supermancho

    “解析而非验证”可以重述为:

    “期望解析器完成类型验证”

    这显然只是把问题从类型层面转移到了其他地方。相比之下,值验证是一个更简单的问题,可以作为独立的应用特定检查来处理。

  4. jelder

    这篇文章很棒。Alexis King 实际上曾表示,如果她早知道“解析而非验证”会如此流行,她本会用一种比 Haskell 更广泛使用的语言来写这篇文章。

  5. jph

    这是一篇关于 Rust 类型系统优势的好文章。如果你喜欢这篇内容,你可能会好奇如何构建自己的解析能力。我推荐 Rust 的 Winnow 和 Nom 这两个 crate,以及 Rust 的 From 和 Into 这两个 trait。

同日更多故事

2026-09-27