loading...
Cover image for Either Types for Rust

Either Types for Rust

sirech profile image Mario Fernández Originally published at hceris.com on ・4 min read

I’ve written extensively about the Either datatype this year. It’s an excellent way to model errors without resorting to exceptions.

Kotlin has been thus far my go-to language to show this concept. However, I’ve been playing a lot with Rust lately. My first instinct was to explore its functional programming capabilities. Rust really delivers there.

There are plenty of exciting ideas in Rust. The borrow checker, lifecycles, and plenty of others. I’m going to focus on Either in this post, although you might be more familiar with its actual name in the language, Result.

A reason to use Rust

Rust is an increasingly popular language. It’s defined as:

A language empowering everyone to build reliable and efficient software.

Tool

A tool with a simple interface

Is Rust a replacement for, say, Kotlin or Ruby? I don’t think so! A systems programming language like Rust might not be the tool you want to use to write applications, especially if there is no need to have tight memory management.

However, I’ve found a use case where Rust fits very well: CLI Tools. By that, I mean tools like cat that are used from the command line. Typically, they start as shell scripts that inevitably get too hard to read and maintain. With Rust, you can use a pretty high-level language, and package it as a binary that’s extremely easy to distribute, be it in a Docker container or anywhere else.

Go has seen success in this space (envconsul comes to mind). After having been involved with both, I find Rust much more pleasant to use.

Either is actually called Result in Rust

Just in case you missed previous posts, let’s quickly present Either. Either is an entity whose value can be of two different types, called left and right. It represents a computation that can fail. So the Right side is the result in case of success, and the Left one is the error if something goes wrong.

Either

It turns out that Rust has a built-in Either datatype, called Result. It has two possible values, Ok and Err. Rust doesn’t have exceptions, which means that handling error conditions is predominantly done using Result. Great news!

Using Result

Result integrates well into the language. It’s implemented as an enumeration with two possible types.

enum Result<T, E> {
   Ok(T),
   Err(E),
}
Enter fullscreen mode Exit fullscreen mode

Being an enumeration comes in handy when we try to unwrap the value, as we’ll see in a second.

Returning a Result

Any value can be wrapped using Ok and Err as constructors.

let success: Result<i32, &str> = Ok(42);
let failure: Result<i32, &str> = Err("failed :(");
Enter fullscreen mode Exit fullscreen mode

Unwrapping a value

Many operations in Rust return a Result. How do you get the value inside? How do you decide what to do based on the outcome of the operation? The answer to both questions lies in pattern matching.

fn read_state(file: Result<i32,&str>) {
    match file {
        Ok(answer) => println!("Extracted from file {}", answer),
        Err() => println!("Bitter disappointment"),
    }
}
Enter fullscreen mode Exit fullscreen mode

Note how we can decide what to do based on the Result type and extract our data in one operation. That’s convenient. Moreover, pattern matching is exhaustive, so we’re sure we’ve handled every possibility. I love pattern matching.

Chaining

You rarely do just one computation. So, are you expected to unwrap the value, transform it, and rewrap it every time? That’d be very annoying. Instead, let’s use map and flatMap (called and_then in Rust) to apply a function and get a new Result. If you remember, these methods are biased, so they’ll never be applied to an error case.

pub fn from_file(file_name: &str) -> anyhow::Result<Self> {
    fs::read_to_string(file_name)
        .and_then(|content| serde_json::from_str(&content))
}
Enter fullscreen mode Exit fullscreen mode

Flat syntax

One disadvantage of modeling errors with Result/Either is that your code can become quite nested as you operate on the data contained within it. The do notation, coming from Haskell, attempts to address this, though it’s not without detractors.

Luckily, Rust has a solution for that as well! The question mark operator. Using it, you can extract the data from an Ok result or return directly the Err if it didn’t work. It looks like this:

pub fn from_file(file_name: &str) -> anyhow::Result<Self> {
    let content = fs::read_to_string(file_name)?;
    let result = serde_json::from_str(&content)?;
    Ok(result)
}
Enter fullscreen mode Exit fullscreen mode

Both read_to_string and from_str can fail, thus returning a Result. If that happens, computations stop, and the method will return an error. The code remains readable without compromising its security.

It combines well with the anyhow crate. If you have multiple kinds of errors in your method, anyhow helps with the propagation. If they implement std::error::Error, that is.

Conclusion

Who said you couldn’t use functional programming concepts in a systems programming language like Rust? I’ve been reading a lot of Go code lately, and this code feels so much more readable than the endless list of if err != nil spread through it. If only Kubernetes stuff would be written in Rust!

Discussion

pic
Editor guide
Collapse
k33g_org profile image
Senior Enterprise Geek 🦊 🤖🌼

I thought that the Left type of Either is not always an Exception, contrary to the Failed or Err type of a Result

Collapse
sirech profile image
Mario Fernández Author

it doesn't have to be an error, although it's usually used like that. The fact that it is right biased sort of guides to that use, I'd say.

Collapse
k33g_org profile image
Senior Enterprise Geek 🦊 🤖🌼

oh ok, good to know, thanks 😃