validated
The cumulative sibling of Result
and Either
.
The Validated
type has special FromIterator
instances that enable all errors in a sequence to be reported, not just the first one.
Motivation
We might think of Iterator::collect
as being for consolidating the result of some chained iteration into a concrete container, like Vec
.
let v = vec![1,2,3];
assert_eq!(vec![2,4,6], v.into_iter().map(|n| n * 2).collect::<Vec<u32>>());
But collect
isn't limited to this; it can be used to "fold" down into any type you like, provided that it implements FromIterator
. Consider the effects of such an impl
for Result
:
let v: Vec<u32> = vec![1, 2, 3];
let r: Result<Vec<u32>, &str> = v
.into_iter()
.map(|n| if n % 2 == 0 { Err("Oh no!") } else { Ok(n * 2) })
.collect();
assert_eq!(Err("Oh no!"), r);
The Result
has been "interweaved" and pulled back out. Critically, this collect
call short-circuits; n * 2
is never called for 3
, since the map
"fails" at 2
. This is useful when we require a sequence of IO actions to all succeed and we wish to cancel remaining operations as soon as any error occurs.
But what if we don't want to short circuit? What if we want a report of all the errors that occurred?
Validated
Cumulative Errors and Consider three cases where we'd want a report of all errors, not just the first one:
- Form input validation.
- Type checking.
- Concurrent IO.
In the first case, if a user makes several input mistakes, it's the best experience for them if all errors are reported at once so that they can make their corrections in a single pass.
In the second case, knowing only the first detected type error might not actually be the site of the real issue. We need everything that's broken to be reported so we can make the best decision of what to fix.
In the third case, it may be that halting your entire concurrent job upon detection of a single failure isn't appropriate. You might instead want everything to finish as it can, and then collect a bundle of errors at the end.
The Validated
type accomodates these use cases; it is a "cumulative Result
".
use validated::Validated::{self, Good, Fail};
use nonempty::NonEmpty;
let v = vec![Good(1), Validated::fail("No!"), Good(3), Validated::fail("Ack!")];
let r: Validated<Vec<u32>, &str> = Fail(NonEmpty::from(("No!", vec!["Ack!"])));
assert_eq!(r, v.into_iter().collect());
NonEmpty
Use of In the spirit of "make illegal states unrepresentable", the Fail
variant of Validated
contains a NonEmpty
, a non-empty Vec
. NonEmpty
can do everything that Vec
can do, plus some additional benefits. In the case of this crate, this representation forbids the otherwise meaningless Fail(vec![])
.
In other words, if you have a Validated
, you either have a concrete T
, or at least one E
.
Features
rayon
: EnableFromParallelIterator
instances forValidated
.
Resources
License: MIT