Result<>

enum Result<T, E> {
   Ok(T),
   Err(E),
}

T is the useful bit, the thing you want.

But what is E?

In rust, the std::result::Result type is the equivalent to what most go programmers are familiar with with the if err pattern.

Results have to be used, usually by a match expression. The compiler will emit a warning since it is annotated #[must_use].

To make life more ergonomic, the ? operator in rust is syntactic sugar for a match with an Err(err) => return Err(From::from(err)); arm to propagate errors up the stack, converting them (with From::from), as necessary.

But what values satisfy E, the type wrapped by Err.

The Error Trait

std::error::Error is a Trait that is usually implemented for values used as E. The trait, by itself, provides the source function, but it is common to also implement Debug and Display for E too.

It can be quite a burden to always write custom Error types in rust, but there are some shortcuts we can take since most objects probably already have an impl for these traits.

Main Results

Some cool magic arises when using Errors in the main() function. This is thanks to the Termination trait.

Errs can be simple static values, even the Unit value.

fn main() -> Result<(), ()> {
  Ok(())
}
fn main() -> Result<(), ()> {
  Err(())
}
fn main() -> Result<(), &'static str> {
  Err("derp")
}
fn main() -> Result<(), bool> {
  Err(true)
}
fn main() -> Result<(), u8> {
  Err(42)
}

Which let’s us use the ? operator.

fn main() -> Result<(), ()> {
  foo()?;
}

Or we can use a library like anyhow.

fn main() -> anyhow::Result<()> {
  foo()?;
}

Which has a helper macro for creating Errors from a string.

fn main() -> anyhow::Result<()> {
  Err(anyhow!("a very specific problem"))
}

Boxed Errors

Boxed errors work like normal.

fn main() -> Result<(), Box<dyn std::error::Error>> {
  Ok(())
}

We can use the ? operator.

fn main() -> Result<(), Box<dyn std::error::Error>> {
  foo()?;
}

But since we can box the Error trait, we can compose errors from different libraries.

fn main() -> Result<(), Box<dyn std::error::Error>> {
  // Could early return an AddrParseError
  let addr: std::net::SocketAddr = "127.0.0.1:8080".parse()?;

  // Could early return an std::io::Error
  let file = std::fs::File::open("foo.txt")?;

  // If we reach here, everything is okay.
  Ok(())
}

This even works for async code too.

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
  foo().await?;
}

Test Results

#[test]
fn test_foo() -> anyhow::Result<()> {
  foo()?;
}
#[test]
fn test_foo() -> Result<(), Box<dyn std::error::Error>> {
  foo()?;
}
#[test]
fn test_fail() -> Result<(), i16> {
  Err(-1)
}