Rust comes out of the box with a
Result<T, E> type in its standard library. For those not familiar with it, it is a union-like enum type where
T is a type parameter denoting the kind object held in a
Result in the success case (
E is a type paramter denoting the kind of error object held in the failure case (
Result::Err<E>). In Scala, this is represented in the standard library as
Either[+A, +B], where the the success and error type params are swapped (traditionally, the one on the left stands for error and the one on the right is…well, right).
Result comes with really good support for what I call “early return on error”. That is, you can use
and_then (flatMap in some other languages) to transform them, and if there’s an error at an intermediate step, the chain returns early with a
1 2 3 4 5 6
But .. what happens when you have multiple
Results that are independent of each other, and you want to accumulate not only their collective success case, but also all their collective errors in the failure case?
By the way, to take a dive into the deep end, jump straight to Validated’s Rustdocs.
Next, let’s add a few imports.
Suppose we have a
Person struct defined as follows:
1 2 3 4 5 6
And, we have 3 methods that produce age, name and email for us, but all could potentially fail with a
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
In real life, these methods would probably be taking an HTML form as an argument and doing some kind of parsing/validation or making calls to a service somewhere, but for simplicity, in our example, each of them takes a single argument that will let us toggle between the success and error cases.
Having set all that up, actually using
Validated to accumulate our
Results is actually very simple:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
As you can see, all we need to do is call
into_validated() on a given
Result to kick off the validation context, and use
+ to add subsequent
Results into it. At the end, you call
into_result() on the
Validated to turn it back into a
Result and map on the
HList that is contained inside. Inside the lambda, we destructure the
HList using the
hlist_pat! macro, and then instantiate our
Oh, in case it isn’t obvious, the
hlist passed to the lambda when we map is statically typed in the order that your
Results were added into the
Validated context, so your code is completely type safe. If you want to learn more about HLists in Frunk, check out this blog post.
Having said that, perhaps in the success case, not much has really changed in comparison to using naked
Results. That is, you could have gotten here simply by chaining with
and_then. But take a look at what happens when one or more of these fail:
1 2 3 4 5 6 7 8 9
As you can see, the failure case is more interesting because
Validated gives us the ability to accumulate all errors cleanly. For operations like parsing user input or checking parameters passed into our API, this non-early-abort behaviour is highly desirable compared with telling the user what went wrong One. Thing. At. At. Time.
Validateds can also be appended to each other:
1 2 3 4 5 6 7 8
Validated out for a spin and send suggestions, comments, PRs ! I’ve found this abstraction to be helpful in the Scala world so I’m eager to hear impressions from Rustaceans.