Parse, Don't Validate and Type-Driven Design in Rust
This post translates the 'Parse, Don't Validate' philosophy from Haskell to Rust, advocating for encoding invariants directly into types rather than relying on runtime checks. It demonstrates how Rust's rich type system can enhance API design by making illegal states unrepresentable and proving data correctness at compile-time. The author makes a compelling case for leveraging type-driven design to create more robust, clear, and resilient code, a concept highly valued by the Hacker News community.
The Lowdown
The article introduces the 'Parse, Don't Validate' principle, traditionally explained in Haskell, and recontextualizes it for Rust programmers. The core idea is to leverage Rust's type system to define types that inherently guarantee certain properties, thereby eliminating the need for repetitive runtime validation and improving code robustness.
- The author begins with a simple
divide_by_zeroexample, contrasting runtime panics andOption<f32>(fallible return types) with a type-driven approach using aNonZeroF32newtype. - This
NonZeroF32type ensures that the divisor can never be zero, pushing validation to the point of type construction rather than function execution. This "strengthens" function parameters instead of "weakening" return types. - Another example,
NonEmptyVec, demonstrates how guaranteeing a collection's non-emptiness at the type level removes redundant checks and makes code more resilient to refactoring errors. - Real-world analogies are drawn to
String(aVec<u8>that parses for UTF-8 validity) andserde_jsondeserialization, where structured types prevent runtime errors common with genericValueparsing. - Two key maxims of Type-Driven Design are highlighted: making illegal states unrepresentable (e.g., a
NonZeroF32cannot represent zero) and proving invariants as early as possible to prevent issues like "shotgun parsing" and security vulnerabilities. - Practical recommendations include using semantic enums instead of raw
bools for clarity and considering parsing functions over simpleverifyfunctions when a more structured type can represent validated data.
In conclusion, the article champions the creation of more specific types to encapsulate data invariants, arguing that while it might introduce some verbosity or ergonomic challenges (due to Rust's current features), it ultimately leads to significantly clearer, more robust, and compile-time-verified software. The author encourages full utilization of Rust's powerful type system for better program design.