[HN Gopher] Parse, Don't Validate and Type-Driven Design in Rust
___________________________________________________________________
Parse, Don't Validate and Type-Driven Design in Rust
Author : todsacerdoti
Score : 89 points
Date : 2026-02-21 19:40 UTC (3 hours ago)
(HTM) web link (www.harudagondi.space)
(TXT) w3m dump (www.harudagondi.space)
| dang wrote:
| Recent and related: _Parse, Don 't Validate (2019)_ -
| https://news.ycombinator.com/item?id=46960392 - Feb 2026 (172
| comments)
|
| also:
|
| _Parse, Don't Validate - Some C Safety Tips_ -
| https://news.ycombinator.com/item?id=44507405 - July 2025 (73
| comments)
|
| _Parse, Don 't Validate (2019)_ -
| https://news.ycombinator.com/item?id=41031585 - July 2024 (102
| comments)
|
| _Parse, don 't validate (2019)_ -
| https://news.ycombinator.com/item?id=35053118 - March 2023 (219
| comments)
|
| _Parse, Don 't Validate (2019)_ -
| https://news.ycombinator.com/item?id=27639890 - June 2021 (270
| comments)
|
| _Parsix: Parse Don 't Validate_ -
| https://news.ycombinator.com/item?id=27166162 - May 2021 (107
| comments)
|
| _Parse, Don't Validate_ -
| https://news.ycombinator.com/item?id=21476261 - Nov 2019 (230
| comments)
|
| _Parse, Don 't Validate_ -
| https://news.ycombinator.com/item?id=21471753 - Nov 2019 (4
| comments)
|
| (p.s. these links are just to satisfy extra-curious readers - no
| criticism is intended! I add this because people sometimes assume
| otherwise)
| jaggederest wrote:
| You can go even further with this in other languages, with things
| like dependent typing - which can assert (among other interesting
| properties) that, for example, something like
| get_elem_at_index(array, index)
|
| cannot ever have index outside the bounds of the array, but
| checked statically at compilation time - and this is the key,
| without knowing a priori what the length of array is.
|
| "In Idris, a length-indexed vector is Vect n a (length n is in
| the type), and a valid index into length n is Fin n ('a natural
| number strictly less than n')."
|
| Similar tricks work with division that might result in inf/-inf,
| to prevent them from typechecking, and more subtle implications
| in e.g. higher order types and functions
| VorpalWay wrote:
| How does that work? If the length of the array is read from
| stdin for example, it would be impossible to know it at compile
| time. Presumably this is limited somehow?
| jaggederest wrote:
| If the length is read from outside the program it's an IO
| operation, not a static variable, but there are generally
| runtime checks in addition to the type system. Usually you
| solve this as in the article, with a constructor that checks
| it - so you'd have something like "Invalid option: length = 5
| must be within 0-4" when you tried to create the Fin n from
| the passed in value
| ratorx wrote:
| It doesn't have to be a compile time constant. An alternative
| is to prove that when you are calling the function the index
| is always less than the size of the vector (a dynamic
| constraint). You may be able to assert this by having a
| separate function on the vector that returns a constrained
| value (eg. n < v.len()).
| mdm12 wrote:
| One option is dependent pairs, where one value of the pair
| (in this example) would be the length of the array and the
| other value is a type which depends on that same value (such
| as Vector n T instead of List T).
|
| Type-Driven Development with Idris[1] is a great introduction
| for dependently typed languages and covers methods such as
| these if you're interested (and Edwin Brady is a great
| teacher).
|
| [1] https://www.manning.com/books/type-driven-development-
| with-i...
| marcosdumay wrote:
| If you check that the value is inside the range, and execute
| some different code if it's not, then congratulations, you
| now know at compile time that the number you will read from
| stdin is in the right range.
| dernett wrote:
| Not sure about Idris, but in Lean `Fin n` is a struct that
| contains a value `i` and a proof that `i < n`. You can read
| in the value `n` from stdin and then you can do `if h : i <
| n` to have a compile-time proof `h` that you can use to
| construct a `Fin n` instance.
| esafak wrote:
| I wish dependent types were more common :(
| satvikpendem wrote:
| Rust has some libraries that can do dependent typing too, based
| on macros. For example: https://youtube.com/watch?v=JtYyhXs4t6w
|
| Which refers to https://docs.rs/anodized/latest/anodized/
| cmovq wrote:
| Dividing a float by zero is usually perfectly valid. It has
| predictable outputs, and for some algorithms like collision
| detection this property is used to remove branches.
| woodruffw wrote:
| I think "has predictable outputs" is less valuable than "has
| expected outputs" for most workloads. Dividing by zero almost
| always reflects an unintended state, so proceeding with the
| operation means compounding the error state.
|
| (This isn't to say it's always wrong, but that having it be an
| error state by default seems very reasonable to me.)
| noitpmeder wrote:
| This reminds me a bit of a recent publication by Stroustrup about
| using concepts... in C++ to validate integer conversions
| automatically where necessary.
|
| https://www.stroustrup.com/Concept-based-GP.pdf {
| Number<unsigned int> ii = 0; Number<char> cc = '0';
| ii = 2; // OK ii = -2; // throws cc = i; // OK
| if i is within cc's range cc = -17; // OK if char is
| signed; otherwise throws cc = 1234; // throws if a char
| is 8 bits }
| strawhatguy wrote:
| The alternative is one type, with many functions that can operate
| on that type.
|
| Like how clojure basically uses maps everywhere and the whole
| standard library allows you to manipulate them in various ways.
|
| The main problem with the many type approach is several same it
| worse similar types, all incompatible.
| packetlost wrote:
| I don't really get why this is getting flagged, I've found this
| to be true but more of a trade off than a pure benefit. It also
| is sort of besides the point: you always need to parse inputs
| from external, usually untrusted, sources.
| doublesocket wrote:
| Agree with this. Mismatching types are generally an indicator
| of an underlying issue with the code, not the language
| itself. These are areas AI can be helpful flagging potential
| problems.
| fiddlerwoaroof wrote:
| Yeah, there's something of a tension between the Perlis quote
| "It is better to have 100 functions operate on one data
| structure than 10 functions on 10 data structures" and Parse,
| don't validate.
|
| The way I've thought about it, though, is that it's possible to
| design a program well either by encoding your important
| invariants in your types or in your functions (especially
| simple functions). In dynamically typed languages like Clojure,
| my experience is that there's a set of design practices that
| have a lot of the same effects as "Parse, Don't Validate"
| without statically enforced types. And, ultimately, it's a
| question of mindset which style you prefer.
| strawhatguy wrote:
| There's probably a case for both. Core logic might benefit
| from hard types deep in the bowels of unchanging engine.
|
| The real world often changes though, and more often than not
| the code has to adapt, regardless of how elegant are systems
| are designed.
| marcosdumay wrote:
| There are more than two alternatives, since functions can
| operate in more than one type.
| Rygian wrote:
| This sounds like the "stringly typed language" mockery of some
| languages. How is it actually different?
| sam0x17 wrote:
| btw the "quoth" crate makes it really really easy to implement
| scannerless parsing in rust for arbitrary syntax, use it on many
| of my projects
| IshKebab wrote:
| Interesting looking crate. You don't seem to have any examples
| at all though so I wouldn't say it makes it easy!
| hutao wrote:
| Note that the division-by-zero example used in this article is
| not the best example to demonstrate "Parse, Don't Validate,"
| because it relies on encapsulation. The principle of "Parse,
| Don't Validate" is best embodied by functions that transform
| untrusted data into some data type which is _correct by
| construction_.
|
| Alexis King, the author of the original "Parse, Don't Validate"
| article, also published a follow-up, "Names are not type safety"
| [0] clarifying that the "newtype" pattern (such as hiding a
| nonzero integer in a wrapper type) provide weaker guarantees than
| correctness by construction. Her original "Parse, Don't Validate"
| article also includes the following caveat:
|
| > Use abstract datatypes to make validators "look like" parsers.
| Sometimes, making an illegal state truly unrepresentable is just
| plain impractical given the tools Haskell provides, such as
| ensuring an integer is in a particular range. In that case, use
| an abstract newtype with a smart constructor to "fake" a parser
| from a validator.
|
| So, an abstract data type that protects its inner data is really
| a "validator" that tries to resemble a "parser" in cases where
| the type system itself cannot encode the invariant.
|
| The article's second example, the non-empty vec, is a better
| example, because it encodes within the type system the invariant
| that one element must exist. The crux of Alexis King's article is
| that programs should be structured so that functions return data
| types designed to be correct by construction, akin to a parser
| transforming less-structured data into more-structured data.
|
| [0] https://lexi-lambda.github.io/blog/2020/11/01/names-are-
| not-...
| Sharlin wrote:
| Even the newtype-based "parse, don't validate" is tremendously
| useful in practice, though. The big thing is that if you have a
| bare string, you don't know "where it's been". It doesn't carry
| with it information whether it's already been validated. Even
| if a newtype can't provide you full correctness by
| construction, it's vastly easier to be convinced of the
| validity of an encapsulated value compared to a naked one.
|
| For full-on parse-don't-validate, you essentially need a
| dependent type system. As a more light-weight partial solution,
| Rust has been prototyping pattern types, which are types
| constrained by patterns. For instance a range-restricted
| integer type could be simply spelled `i8 is 0..100`, or a
| nonempty slice as `[T] is [_, ..]`. Such a feature would
| certainly make correctness-by-construction easier in many
| cases.
|
| The non-empty list implemented as a (T, Vec<T>) is, btw, a nice
| example of the clash between practicality and theoretical
| purity. It can't offer you a slice (consecutive view) of its
| elements without storing the first element twice (which
| requires immutability and that T: Clone, unlike normal Vec<T>),
| which makes it fairly useless as a vector. It's okay if you
| consider it just an abstract list with a more restricted
| interface.
| rapnie wrote:
| You can also search for "make invalid states
| impossible/unrepresentable" [0] to find more info on related
| practices. See "domain modeling made functional" [0] as a nice
| example
|
| [0] https://geeklaunch.io/blog/make-invalid-states-
| unrepresentab...
|
| [1] https://www.youtube.com/watch?v=2JB1_e5wZmU
| hutao wrote:
| The phrasing that I hear more often is "make illegal states
| unrepresentable"; both the submitted article and Alexis
| King's original article use this phrase. At least according
| to https://fsharpforfunandprofit.com/posts/designing-with-
| types..., it originates from Yaron Minsky (a programmer at
| Jane Street who is prominent in the OCaml community).
|
| EDIT: Parent comment was edited to amend the
| "impossible/unrepresentable" wording
| rapnie wrote:
| Yes, sorry. I thought to add some resources to it, or it
| would be a too vague comment and found the better phrasing.
| fph wrote:
| The article quickly mentions implementing addition:
|
| ```
|
| impl Add for NonZeroF32 { ... }
|
| impl Add<f32> for NonZeroF32 { ... }
|
| impl Add<NonZeroF32> for f32 { ... }
|
| ```
|
| What type would it return though?
| alfons_foobar wrote:
| Would have to be F32, no? I cannot think of any way to enforce
| "non-zero-ness" of the result without making it return an
| optional Result<NonZeroF32>, and at that point we are basically
| back to square one...
| MaulingMonkey wrote:
| > Would have to be F32, no?
|
| Generally yes. `NonZeroU32::saturating_add(self, other: u32)`
| is able to return `NonZeroU32` though! ( https://doc.rust-
| lang.org/std/num/type.NonZeroU32.html#metho... )
|
| > I cannot think of any way to enforce "non-zero-ness" of the
| result without making it return an optional
| Result<NonZeroF32>, and at that point we are basically back
| to square one...
|
| `NonZeroU32::checked_add(self, other: u32)` basically does
| this, although I'll note it returns an `Option` instead of a
| `Result` ( https://doc.rust-
| lang.org/std/num/type.NonZeroU32.html#metho... ), leaving you
| to `.map_err(...)` or otherwise handle the edge case to your
| heart's content. Niche, but occasionally what you want.
| alfons_foobar wrote:
| > `NonZeroU32::saturating_add(self, other: u32)` is able to
| return `NonZeroU32` though!
|
| I was confused at first how that could work, but then I
| realized that of course, with _unsigned_ integers this
| works fine because you cannot add a negative number...
| the__alchemist wrote:
| The examples in question propagate complexity throughout related
| code. I think this is a case I see frequently in Rust of using
| too many abstractions, and its associated complexities.
|
| I would just (as a default; the situation varies)... validate
| prior to the division and handle as appropriate.
|
| The analogous situation I encounter frequently is indexing, e.g.
| checking if the index is out of bounds. Similar idea; check;
| print or display an error, then fail that computation without
| crashing the program. Usually an indication of some bug, which
| can be tracked down. Or, if it's an array frequently indexed, use
| a (Canonical for Rust's core) `get` method on the whatever struct
| owns the array. It returns an Option.
|
| I do think either the article's approach, or validating is better
| than runtime crashes! There are many patterns in programming.
| Using Types in this way is something I see a lot of in OSS rust,
| but it is not my cup of tea. Not heinous in this case, but I
| think not worth it.
|
| This is the key to this article's philosophy, near the bottom:
|
| > I love creating more types. Five million types for everyone
| please.
___________________________________________________________________
(page generated 2026-02-21 23:00 UTC)