|
| 1 | +--- |
| 2 | +minutes: 5 |
| 3 | +--- |
| 4 | + |
| 5 | +# Parse, Don't Validate |
| 6 | + |
| 7 | +The newtype pattern can be leveraged to enforce _invariants_. |
| 8 | + |
| 9 | +```rust |
| 10 | +pub struct Username(String); |
| 11 | + |
| 12 | +impl Username { |
| 13 | + pub fn new(username: String) -> Result<Self, InvalidUsername> { |
| 14 | + if username.is_empty() { |
| 15 | + return Err(InvalidUsername::CannotBeEmpty) |
| 16 | + } |
| 17 | + if username.len() > 32 { |
| 18 | + return Err(InvalidUsername::TooLong { len: username.len() }) |
| 19 | + } |
| 20 | + // Other validation checks... |
| 21 | + Ok(Self(username)) |
| 22 | + } |
| 23 | + |
| 24 | + pub fn as_str(&self) -> &str { |
| 25 | + &self.0 |
| 26 | + } |
| 27 | +} |
| 28 | +# pub enum InvalidUsername { |
| 29 | +# CannotBeEmpty, |
| 30 | +# TooLong { len: usize }, |
| 31 | +# } |
| 32 | +``` |
| 33 | + |
| 34 | +<details> |
| 35 | + |
| 36 | +- The newtype pattern, combined with Rust's module and visibility system, can be |
| 37 | + used to _guarantee_ that instances of a given type satisfy a set of |
| 38 | + invariants.\ |
| 39 | + In the example above, the raw `String` stored inside the |
| 40 | + `Username` struct can't be accessed directly from other modules or crates, |
| 41 | + since it's not marked as `pub` or `pub(in ...)`. Consumers of the `Username` |
| 42 | + type are forced to use the `new` method to create instances. In turn, `new` |
| 43 | + performs validation, thus ensuring that all instances of `Username` satisfy |
| 44 | + those checks. |
| 45 | + |
| 46 | +- The `as_str` method allows consumers to access the raw string representation |
| 47 | + (e.g. to store it in a database) but, thanks to Rust's borrow checker, |
| 48 | + they can't modify it. |
| 49 | + |
| 50 | +- Stress the importance of evaluating _the entire API surface_ exposed by a newtype |
| 51 | + to determine if invariants are indeed bullet-proof.\ |
| 52 | + It is crucial to consider all possible interactions, including trait implementations, |
| 53 | + that may allow users to bypass the invariants. For example, if the `Username` |
| 54 | + type implements the `DerefMut` trait, users can modify the underlying string |
| 55 | + directly, bypassing the validation checks in `new`. |
| 56 | + |
| 57 | +- Type-level invariants have second-order benefits.\ |
| 58 | + The input is validated once, at the boundary, and the rest of the program can rely |
| 59 | + on the invariants being upheld. We can avoid redundant validation and |
| 60 | + "defensive programming" checks throughout the program, reducing noise and |
| 61 | + improving performance. |
| 62 | + |
| 63 | +</details> |
0 commit comments