Skip to content

Commit ac21925

Browse files
Parse, don't validate
1 parent e262c0f commit ac21925

File tree

2 files changed

+64
-0
lines changed

2 files changed

+64
-0
lines changed

src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,7 @@
435435
- [Leveraging the Type System](idiomatic/leveraging-the-type-system.md)
436436
- [Newtype Pattern](idiomatic/leveraging-the-type-system/newtype-pattern.md)
437437
- [Semantic Confusion](idiomatic/leveraging-the-type-system/newtype-pattern/semantic-confusion.md)
438+
- [Parse, Don't Validate](idiomatic/leveraging-the-type-system/newtype-pattern/parse-don-t-validate.md)
438439

439440
---
440441

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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

Comments
 (0)