Another example, let's say you're parsing a subscriber name for an email list, and you have a few checks such as whether it's too long, has whitespace, orcontains forbidden characters:
pub fn parse(s: String) -> SubscriberName {
let is_empty_or_whitespace = s.trim().is_empty();
let is_too_long = s.graphemes(true).count() > 256;
let forbidden_characters = ['/', '(', ')', '"', '<', '>', '\\', '{', '}'];
let contains_forbidden_characters = s
.chars()
.any(|g| forbidden_characters.contains(&g));
if is_empty_or_whitespace || is_too_long || contains_forbidden_characters {
panic!("{} is not a valid subscriber name.", s)
} else {
Self(s)
}
}
And let's say your code to add a new subscriber to the email list
only accepts a SubscriberName,
not a String:
pub struct NewSubscriber {
pub email: SubscriberEmail,
pub name: SubscriberName,
}
Because `parse` gives back a SubscriberName if and
only if all of the parse checks pass, you simply
cannot add a new subscriber without all checks passing; the function will simply fail otherwise.
This example comes from the excellent book Zero To Production In Rust, which has a chapter on type-driven development [0].
[0] https://zero2prod.com