Live data from Hacker News

Parse, Don't Validate and Type-Driven Design in Rust

harudagondi.space

31–40 of 89 posts

Re: Parse, Don't Validate and Type-Driven Design in Rust

#32
post #23

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, "Na…

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) 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), which makes it fairly useless as a vector. It's okay if you consider it just an abstract list with a more restricted interface.

Re: Parse, Don't Validate and Type-Driven Design in Rust

#33
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.

Re: Parse, Don't Validate and Type-Driven Design in Rust

#34
post #23

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, "Na…

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

Re: Parse, Don't Validate and Type-Driven Design in Rust

#35
post #34
post #23

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, "Na…

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

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

Re: Parse, Don't Validate and Type-Driven Design in Rust

#36

Earlier quoted context omitted.

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 , and at that point we are basically back to square one...

> 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 , and at that point we are basically back to square one... `NonZeroU32::checked_add(self, other: u32)` basical…

> `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...

Re: Parse, Don't Validate and Type-Driven Design in Rust

#37
post #35
post #34

Earlier quoted context omitted.

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

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…

Yes, sorry. I thought to add some resources to it, or it would be a too vague comment and found the better phrasing.

Re: Parse, Don't Validate and Type-Driven Design in Rust

#38

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.

It's not an alternative.

Start with a more dynamic type, do stuff that doesn't care about the shape, parse into a more precise type, do stuff that relies on the additional invariants, drop back into the more dynamic type again.

Re: Parse, Don't Validate and Type-Driven Design in Rust

#39

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.

I find a balance is important. You can do nominal typing in a structural type system with branding, and you can kinda do structural typing in a nominal type system, but it's not as ergonomic. But you should probably end up doing a mix of both.

Re: Parse, Don't Validate and Type-Driven Design in Rust

#40
The `try_roots` example here is actually a _counterexample_ to the author's main argument. They explicitly ignore the "negative discriminant" case. What happens if we consider it?

If we take their "parse" approach, then the types of the arguments a, b, and c have to somehow encode the constraint `b^2 - 4ac >= 0`. This would be a total mess--I can't think of any clean way to do this in Rust. It makes _much_ more sense to simply return an Option and do the validation within the function.

In general, I think validation is often the best way to solve the problem. The only counterexample, which the author fixates on in the post, is when one particular value is constrained in a clean, statically verifiable way. Most of the time, validation is used to check (possibly complex) interactions between multiple values, and "parsing" isn't at all convenient.

Post reply on HN