validity
Compile-time enforcement of arbitrary invariants
In general, it is a good idea to make invalid states unrepresentable. This can often be achieved using the type system, but there is a limit to the type system's power.
For example, imagine you're trying to represent a user's email address. You might write a struct like this:
;
But you don't want any old string to be allowed, you might want to validate that it is actually a valid email address. So your code might look like:
This works, but we're "hiding" a particular invariant from the compiler: do_stuff_with_email cannot be called with an Email that might return false when passed to validate_email.
However, with validity, we can make a few small changes to tell the compiler about this invariant:
- Firstly, define what it means for an email to be "valid"
- Then, we mark
do_stuff_with_valid_emailas needing a valid email address:
The only way to get a Valid<Email> is by going through the validate function and handling any potential errors:
Great! We now can't forget to validate emails, since there is no other way to create a Valid<T>.
Warning - determinism
Note, this implementation assumes that is_valid is deterministic (i.e. it gives the same result every time it's called). For example, the following implementation can allow an invalid value to exist inside a Valid<T>: