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 (Result::Ok<T>), and 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).

By default, Result comes with really good support for what I call “early return on error”. That is, you can use map, 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 Result::Err<E> :

1
2
3
4
5
6
fn double_arg(mut argv: env::Args) -> Result<i32, String> {
    argv.nth(1)
        .ok_or("Please give at least one argument".to_owned())
        .and_then(|arg| arg.parse::<i32>().map_err(|err| err.to_string()))
        .map(|n| 2 * n)
}

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?

Let’s have a look at Validated in Frunk (which is itself inspired by Validated in Cats)

Frunk is published to Crates.io, so to begin, add the crate to your list of dependencies:

1
2
[dependencies]
frunk = "0.1.9"

By the way, to take a dive into the deep end, jump straight to Validated’s Rustdocs.

Imports

Next, let’s add a few imports.

1
2
use frunk::hlist::*; // brings the .to_tuple2() method in scope so we can destructure our HList easily
use frunk::validated::*;

Scenario

Suppose we have a Person struct defined as follows:

1
2
3
4
5
6
#[derive(PartialEq, Eq, Debug)]
struct Person {
    age: i32,
    name: String,
    email: String,
}

And, we have 3 methods that produce age, name and email for us, but all could potentially fail with a Nope error.

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
/// For demonstrations purposes only
#[derive(PartialEq, Eq, Debug)]
pub enum YahNah {
    Yah,
    Nah,
}

/// Our Errors
#[derive(PartialEq, Eq, Debug)]
pub enum Nope {
    NameNope,
    AgeNope,
    EmailNope,
}

fn get_name(yah_nah: YahNah) -> Result<String, Nope> {
    match yah_nah {
        YahNah::Yah => Result::Ok("James".to_owned()),
        _ => Result::Err(Nope::NameNope),
    }
}

fn get_age(yah_nah: YahNah) -> Result<i32, Nope> {
    match yah_nah {
        YahNah::Yah => Result::Ok(32),
        _ => Result::Err(Nope::AgeNope),
    }
}

fn get_email(yah_nah: YahNah) -> Result<String, Nope> {
    match yah_nah {
        YahNah::Yah => Result::Ok("[email protected]".to_owned()),
        _ => Result::Err(Nope::EmailNope),
    }
}

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.

Using Validated

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
let v = get_name(YahNah::Yah).into_validated() +
           get_age(YahNah::Yah) +
           get_email(YahNah::Yah);

// Turn it into a result and then map, passing a lambda that takes the HList contained inside
let person = v.into_result()
              .map(|hlist| {
                  let hlist_pat!(name, age, email) = hlist;
                  Person {
                      name: name,
                      age: age,
                      email: email,
                  }
              });

assert_eq!(person.unwrap(),
                   Person {
                       name: "James".to_owned(),
                       age: 32,
                       email: "[email protected]".to_owned(),
                   });

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 Person.

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 map and/or 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
let v = get_name(YahNah::Nah).into_validated() +  // get_name will fail
           get_age(YahNah::Yah) +  // get_age will succeed
           get_email(YahNah::Nah); // get_email will fail

let person = v.into_result()
              .map(|_| unimplemented!()); // won't get here anyways

assert_eq!(person.unwrap_err(),
           vec![Nope::NameNope, Nope::EmailNope]);

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.

Oh, Validateds can also be appended to each other:

1
2
3
4
5
6
7
8
let r1: Result<String, String> = Result::Ok(String::from("hello"));
let r2: Result<i32, String> = Result::Ok(1);
let r3: Result<i32, String> = Result::Ok(3);
let v1 = r1.into_validated();
let v2 = r2.into_validated();
let v3 = r3.into_validated();
let comb = v1 + v2 + v3;
assert_eq!(comb, Validated::Ok(hlist!(String::from("hello"), 1, 3)))

Conclusion

Please take 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.

  1. Frunk on Github
  2. Frunk on Crates.io

Comments