Rust's type system turns "Parse, don't validate" into a superpower

Rusty thoughts on "Parse, don't validate"

Alexis King's "Parse, don't validate" pattern gets a Rust-flavored review. The author shows how types like NonEmpty, NonZero, and rust-analyzer's AbsPathBuf encode invariants so you never re-check emptiness or zero. Examples from the standard library, serde, and real projects illustrate gradual parsing and type refinement, with a nod to how dynamic languages handle the same problem.

The core issue is that Vec is fundamentally a type that can be empty; we can carry along a "This one can't be empty, pinky promise!" comment on all the relevant code, but it's not formally checked by anything.
  1. articulatepang

    I prefer a slightly more general rule: Make Illegal States Unrepresentable. The "Parse, don't validate" rule is a special case of MISU.

    What's the difference? MISU applies even when there's no parsing-like transformation happening. For example, if you have a variable that represents the current state of a network connection, and let's say it can be Disconnected, or Connected to some IP address (this is an oversimplification).

    Then one way to do it would be

    struct {

    connected: bool,

    peer_ip: int32

    }

    The trouble is that this allows us to represent an illegal/meaningless state: we're disconnected but there's still some junk old peer_ip hanging in there. Even worse, we might have written

    struct {

    connected: bool,

    peer_ip: Option<int32>

    }

    Now we could have connected = true but peer_ip = None.

    The solution is to use a sum type:

    type connection =

    Disconnected

    | Connected of int32

    (sorry for using made-up syntax; I hope it's clear to anyone familiar with Rust.)

    "Make Illegal States Unrepresentable" applies throughout your program, at every interface between modules or functions in the program, including but not limited to parsing input.

  2. Fluorescence

    Not sure that type is good advice:

    pub struct NonEmpty<T> {

    pub head: T,

    pub tail: Vec<T>,

    }

    You'd have to manually implement the traits to support the ergonomics of slices and iteration and costly reallocation if you need to pass ownership as a Vec:

    I'd expect:

    pub struct NonEmpty<T> {

    v: Vec<T>,

    }

    The constructor would enforce the invariant and then you'd impl Deref and DerefMut for [T] to gain normal len/is_empty/indexing/iteration, passing as &[T] to other funcs and mutating values (which can't break the invariant).

    To mutate length while preserving the invariant it's dealers choice e.g.

    - add .into_vec() for unwrap/mutate/rewrap

    - add invariant preserving mutators of your choice

  3. Supermancho

    "parse don't validate" can be rephrased:

    "expect the type validation from a parser"

    Which is plainly moving the problem around, for types. The value validation is a much simpler problem, as a separate application-specific check.

  4. jelder

    This is great. Alexis King actually stated that, had she known how popular “Parse, Don’t Validate” had been, she would have written it in a language more widely used than Haskell.

  5. jph

    Good article on Rust's strengths with types. If you like this, you may be curious how you might build your own parse capabilities. I like the Rust crates Winnow and Nom, and also the Rust traits From and Into.

More from this day

2026-09-27