Rust error to string

The `Error` type, a wrapper around a dynamic error type.

logo

#[repr(transparent)]

pub struct Error { /* private fields */ }

Expand description

The Error type, a wrapper around a dynamic error type.

Error works a lot like Box<dyn std::error::Error>, but with these
differences:

  • Error requires that the error is Send, Sync, and 'static.
  • Error guarantees that a backtrace is available, even if the underlying
    error type does not provide one.
  • Error is represented as a narrow pointer — exactly one word in
    size instead of two.

Display representations

When you print an error object using “{}” or to_string(), only the outermost
underlying error or context is printed, not any of the lower level causes.
This is exactly as if you had called the Display impl of the error from
which you constructed your anyhow::Error.

Failed to read instrs from ./path/to/instrs.json

To print causes as well using anyhow’s default formatting of causes, use the
alternate selector “{:#}”.

Failed to read instrs from ./path/to/instrs.json: No such file or directory (os error 2)

The Debug format “{:?}” includes your backtrace if one was captured. Note
that this is the representation you get by default if you return an error
from fn main instead of printing it explicitly yourself.

Error: Failed to read instrs from ./path/to/instrs.json

Caused by:
    No such file or directory (os error 2)

and if there is a backtrace available:

Error: Failed to read instrs from ./path/to/instrs.json

Caused by:
    No such file or directory (os error 2)

Stack backtrace:
   0: <E as anyhow::context::ext::StdError>::ext_context
             at /git/anyhow/src/backtrace.rs:26
   1: core::result::Result<T,E>::map_err
             at /git/rustc/src/libcore/result.rs:596
   2: anyhow::context::<impl anyhow::Context<T,E> for core::result::Result<T,E>>::with_context
             at /git/anyhow/src/context.rs:58
   3: testing::main
             at src/main.rs:5
   4: std::rt::lang_start
             at /git/rustc/src/libstd/rt.rs:61
   5: main
   6: __libc_start_main
   7: _start

To see a conventional struct-style Debug representation, use “{:#?}”.

Error {
    context: "Failed to read instrs from ./path/to/instrs.json",
    source: Os {
        code: 2,
        kind: NotFound,
        message: "No such file or directory",
    },
}

If none of the built-in representations are appropriate and you would prefer
to render the error and its cause chain yourself, it can be done something
like this:

use anyhow::{Context, Result};

fn main() {
    if let Err(err) = try_main() {
        eprintln!("ERROR: {}", err);
        err.chain().skip(1).for_each(|cause| eprintln!("because: {}", cause));
        std::process::exit(1);
    }
}

fn try_main() -> Result<()> {
    ...
}
source

impl Error

source

Create a new error object from any error type.

The error type must be threadsafe and 'static, so that the Error
will be as well.

If the error type does not provide a backtrace, a backtrace will be
created here to ensure that a backtrace exists.

source

Create a new error object from a printable error message.

If the argument implements std::error::Error, prefer Error::new
instead which preserves the underlying error’s cause chain and
backtrace. If the argument may or may not implement std::error::Error
now or in the future, use anyhow!(err) which handles either way
correctly.

Error::msg("...") is equivalent to anyhow!("...") but occasionally
convenient in places where a function is preferable over a macro, such
as iterator or stream combinators:

use anyhow::{Error, Result};
use futures::stream::{Stream, StreamExt, TryStreamExt};

async fn demo<S>(stream: S) -> Result<Vec<Output>>
where
    S: Stream<Item = Input>,
{
    stream
        .then(ffi::do_some_work) // returns Result<Output, &str>
        .map_err(Error::msg)
        .try_collect()
        .await
}
source

Wrap the error value with additional context.

For attaching context to a Result as it is propagated, the
Context extension trait may be more convenient than
this function.

The primary reason to use error.context(...) instead of
result.context(...) via the Context trait would be if the context
needs to depend on some data held by the underlying error:

use anyhow::Result;
use std::fs::File;
use std::path::Path;

struct ParseError {
    line: usize,
    column: usize,
}

fn parse_impl(file: File) -> Result<T, ParseError> {
    ...
}

pub fn parse(path: impl AsRef<Path>) -> Result<T> {
    let file = File::open(&path)?;
    parse_impl(file).map_err(|error| {
        let context = format!(
            "only the first {} lines of {} are valid",
            error.line, path.as_ref().display(),
        );
        anyhow::Error::new(error).context(context)
    })
}
source

Get the backtrace for this Error.

In order for the backtrace to be meaningful, one of the two environment
variables RUST_LIB_BACKTRACE=1 or RUST_BACKTRACE=1 must be defined
and RUST_LIB_BACKTRACE must not be 0. Backtraces are somewhat
expensive to capture in Rust, so we don’t necessarily want to be
capturing them all over the place all the time.

  • If you want panics and errors to both have backtraces, set
    RUST_BACKTRACE=1;
  • If you want only errors to have backtraces, set
    RUST_LIB_BACKTRACE=1;
  • If you want only panics to have backtraces, set RUST_BACKTRACE=1 and
    RUST_LIB_BACKTRACE=0.
Stability

Standard library backtraces are only available on the nightly channel.
Tracking issue: rust-lang/rust#53487.

On stable compilers, this function is only available if the crate’s
“backtrace” feature is enabled, and will use the backtrace crate as
the underlying backtrace implementation.

[dependencies]
anyhow = { version = "1.0", features = ["backtrace"] }
source

An iterator of the chain of source errors contained by this Error.

This iterator will visit every error in the cause chain of this error
object, beginning with the error that this error object was created
from.

Example
use anyhow::Error;
use std::io;

pub fn underlying_io_error_kind(error: &Error) -> Option<io::ErrorKind> {
    for cause in error.chain() {
        if let Some(io_error) = cause.downcast_ref::<io::Error>() {
            return Some(io_error.kind());
        }
    }
    None
}
source

The lowest level cause of this error — this error’s cause’s
cause’s cause etc.

The root cause is the last error in the iterator produced by
chain().

source

Returns true if E is the type held by this error object.

For errors with context, this method returns true if E matches the
type of the context C or the type of the error on which the
context has been attached. For details about the interaction between
context and downcasting, see here.

source

Attempt to downcast the error object to a concrete type.

source

Downcast this error object by reference.

Example
// If the error was caused by redaction, then return a tombstone instead
// of the content.
match root_cause.downcast_ref::<DataStoreError>() {
    Some(DataStoreError::Censored(_)) => Ok(Poll::Ready(REDACTED_CONTENT)),
    None => Err(error),
}
source

Downcast this error object by mutable reference.

impl !RefUnwindSafe for Error

impl Send for Error

impl Sync for Error

impl Unpin for Error

impl !UnwindSafe for Error

source

impl<T> Any for Twhere
    T: ‘static + ?Sized,

source

impl<T> Borrow<T> for Twhere
    T: ?Sized,

source

impl<T> BorrowMut<T> for Twhere
    T: ?Sized,

source

impl<T> From<!> for T

source

impl<T> From<T> for T

source

impl<T, U> Into<U> for Twhere
    U: From<T>,

const: unstable · source

Calls U::from(self).

That is, this conversion is whatever the implementation of
From<T> for U chooses to do.

source

impl<T> ToString for Twhere
    T: Display + ?Sized,

source

impl<T, U> TryFrom<U> for Twhere
    U: Into<T>,

The type returned in the event of a conversion error.

const: unstable · source

Performs the conversion.

source

impl<T, U> TryInto<U> for Twhere
    U: TryFrom<T>,

The type returned in the event of a conversion error.

const: unstable · source

Performs the conversion.

This blog covers error handling in Rust with simple examples.

Rust does not have exceptions — it uses the Result enum which is a part of the Rust standard library and it is used to represent either success (Ok) or failure (Err).

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

Enter fullscreen mode

Exit fullscreen mode

When you are writing functions and need to handle potential problems, you can exit the program using the panic! (macro) — this should be rare

panic

fn gen1() -> i32 {
    let mut rng = rand::thread_rng();
    let num = rng.gen_range(1, 10);
    if num <= 5 {
        num
    } else {
        panic!("{} is more than 5", num);
    }
}

Enter fullscreen mode

Exit fullscreen mode

Here we call panic! if the random number is more than 5

To make our intentions clearer, we can return a Result

Using Result

fn gen2() -> Result<i32, String> {
    let mut rng = rand::thread_rng();
    let num = rng.gen_range(0, 10);
    if num <= 5 {
        Ok(num)
    } else {
        Err(String::from(num.to_string() + " is more than 5"))
    }
}

Enter fullscreen mode

Exit fullscreen mode

In this case, if the number is less than equal to 5, we return the Ok variant else, we return an Err with a String message

we could have used an Option return type in this case, but in this case, we are using result since a number more than 5 is an erroneous scenario (in our fictitious use case)

Now we have a method which returns a Result, let’s explore how a caller would use this method. There are a few options:

  • Use panic!
  • Acknowledge the problem and handle it
  • Pass it on

panic (again!)

This is the easiest but most often, the least desirable way to handle problems. Just panicking is a terrible idea. We can make it a little better, if not ideal

use expect

expect is a shortcut — it is a method available on Result which returns the content from the Ok variant if its available. Otherwise, it panics with the passed in message and the Err content

fn caller() -> String {
    let n = gen2().expect("generate failed!");
    n.to_string()
}

Enter fullscreen mode

Exit fullscreen mode

use unwrap

unwrap is similar to expect apart from the fact that it does not allow you to specify a custom message (to be displayed on panic!)

fn caller() -> String {
    let n = gen2().unwrap();
    n.to_string()
}

Enter fullscreen mode

Exit fullscreen mode

We can do a little better with unwrap_or() and use it to return a default value in case of an Err

fn caller() -> String {
    let zero = gen2().unwrap_or(0);
    zero.to_string()
}

Enter fullscreen mode

Exit fullscreen mode

In this case, we return "0" (String form) in case of an error

Handle the problem

fn caller() -> String {
    let r = gen2();
    match r {
        Ok(i) => i.to_string(),
        Err(e) => e,
    }
}

Enter fullscreen mode

Exit fullscreen mode

In this case, we match on the Result: return a String (using i.to_string()) or return the Err content (which also happens to be a String in this case)

Not my problem.. pass it on

What if you did not want to handle the problem and need to propagate it to the caller (in a slightly different way)?

Instead of returning an explicit String, you can return a Result<String,String> where Ok variant will contain a String result and an Err variant will (also) contain a String result explaining with error details

note that a String has been used to represent error (Err) as well. This is just to keep things simple. Typically, you would use a special error type e.g. std::io:Error etc.

Here is one way:

fn caller() -> Result<String, String> {
    let r = gen2();
    match r {
        Ok(i) => Ok(i.to_string()),
        Err(e) => Err(e),
    }
}

Enter fullscreen mode

Exit fullscreen mode

We change the return type to Result<String, String>, invoke the function and match (which returns a Result<i32,String>). All we need is to make sure we return the String conversion of i32. So we match against the returned Result — if its Ok, we return another Ok which contains the String version of i32 i.e. Ok(i) => Ok(i.to_string()). In case of an error, we simply return it as is

? operator

We can simplify this further by using the ? operator. This is similar to the match process wherein, it returns the value in the Ok variant of the Result or exits with by returning Err itself. This is how its usage would look like in our case:

fn caller() -> Result<String, String> {
    let n = gen2()?;
    Ok(n.to_string())
}

Enter fullscreen mode

Exit fullscreen mode

The method is called — notice the ? in the end. All it does it return the value in Ok variant and we return its String version using Ok(n.to_string()). This takes care of the one half (the happy path with Ok) of the Result — what about the other half (Err)? Like I mentioned before, with ? its all taken care of behind the scenes, and the Err with its value is automatically returned from the function!

Please note that the ? operator can be used if you a returns Result or Option or another type that implements std::ops::Try

What if the Err type was different?
You can/will have a situation where you’re required to return a different error type in the Result. In such a case, you will need to implement std::convert::From for your error type. e.g. this won’t work since Result<String, NewErr> is the return type

...
//new error type
struct NewErr {
    ErrMsg: String,
}
...
fn caller5() -> Result<String, NewErr> {
    let n = gen2()?;
    Ok(n.to_string())
}

Enter fullscreen mode

Exit fullscreen mode

You need to tell Rust how to convert from String to NewErr in this case since the return type for the gen2() function is Result<String,String>. This can be done as such:

impl std::convert::From<String> for NewErr {
    fn from(s: String) -> Self {
        NewErr { ErrMsg: s }
    }
}

Enter fullscreen mode

Exit fullscreen mode

Command Line Applications in Rust

Nicer error reporting

We all can do nothing but accept the fact that errors will occur.
And in contrast to many other languages,
it’s very hard not to notice and deal with this reality
when using Rust:
As it doesn’t have exceptions,
all possible error states are often encoded in the return types of functions.

Results

A function like read_to_string doesn’t return a string.
Instead, it returns a Result
that contains either
a String
or an error of some type
(in this case std::io::Error).

How do you know which it is?
Since Result is an enum,
you can use match to check which variant it is:


#![allow(unused)]
fn main() {
let result = std::fs::read_to_string("test.txt");
match result {
    Ok(content) => { println!("File content: {}", content); }
    Err(error) => { println!("Oh noes: {}", error); }
}
}

Unwrapping

Now, we were able to access the content of the file,
but we can’t really do anything with it after the match block.
For this, we’ll need to somehow deal with the error case.
The challenge is that all arms of a match block need to return something of the same type.
But there’s a neat trick to get around that:


#![allow(unused)]
fn main() {
let result = std::fs::read_to_string("test.txt");
let content = match result {
    Ok(content) => { content },
    Err(error) => { panic!("Can't deal with {}, just exit here", error); }
};
println!("file content: {}", content);
}

We can use the String in content after the match block.
If result were an error, the String wouldn’t exist.
But since the program would exit before it ever reached a point where we use content,
it’s fine.

This may seem drastic,
but it’s very convenient.
If your program needs to read that file and can’t do anything if the file doesn’t exist,
exiting is a valid strategy.
There’s even a shortcut method on Results, called unwrap:


#![allow(unused)]
fn main() {
let content = std::fs::read_to_string("test.txt").unwrap();
}

No need to panic

Of course, aborting the program is not the only way to deal with errors.
Instead of the panic!, we can also easily write return:

fn main() -> Result<(), Box<dyn std::error::Error>> {
let result = std::fs::read_to_string("test.txt");
let content = match result {
    Ok(content) => { content },
    Err(error) => { return Err(error.into()); }
};
Ok(())
}

This, however changes the return type our function needs.
Indeed, there was something hidden in our examples all this time:
The function signature this code lives in.
And in this last example with return,
it becomes important.
Here’s the full example:

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let result = std::fs::read_to_string("test.txt");
    let content = match result {
        Ok(content) => { content },
        Err(error) => { return Err(error.into()); }
    };
    println!("file content: {}", content);
    Ok(())
}

Our return type is a Result!
This is why we can write return Err(error); in the second match arm.
See how there is an Ok(()) at the bottom?
It’s the default return value of the function and means
“Result is okay, and has no content”.

Question Mark

Just like calling .unwrap() is a shortcut
for the match with panic! in the error arm,
we have another shortcut for the match that returns in the error arm:
?.

That’s right, a question mark.
You can append this operator to a value of type Result,
and Rust will internally expand this to something very similar to
the match we just wrote.

Give it a try:

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let content = std::fs::read_to_string("test.txt")?;
    println!("file content: {}", content);
    Ok(())
}

Very concise!

Providing Context

The errors you get when using ? in your main function are okay,
but they are not great.
For example:
When you run std::fs::read_to_string("test.txt")?
but the file test.txt doesn’t exist,
you get this output:

Error: Os { code: 2, kind: NotFound, message: "No such file or directory" }

In cases where your code doesn’t literally contain the file name,
it would be very hard to tell which file was NotFound.
There are multiple ways to deal with this.

For example, we can create our own error type,
and then use that to build a custom error message:

#[derive(Debug)]
struct CustomError(String);

fn main() -> Result<(), CustomError> {
    let path = "test.txt";
    let content = std::fs::read_to_string(path)
        .map_err(|err| CustomError(format!("Error reading `{}`: {}", path, err)))?;
    println!("file content: {}", content);
    Ok(())
}

Now,
running this we’ll get our custom error message:

Error: CustomError("Error reading `test.txt`: No such file or directory (os error 2)")

Not very pretty,
but we can easily adapt the debug output for our type later on.

This pattern is in fact very common.
It has one problem, though:
We don’t store the original error,
only its string representation.
The often used anyhow library has a neat solution for that:
similar to our CustomError type,
its Context trait can be used to add a description.
Additionally, it also keeps the original error,
so we get a “chain” of error messages pointing out the root cause.

Let’s first import the anyhow crate by adding
anyhow = "1.0" to the [dependencies] section
of our Cargo.toml file.

The full example will then look like this:

use anyhow::{Context, Result};

fn main() -> Result<()> {
    let path = "test.txt";
    let content = std::fs::read_to_string(path)
        .with_context(|| format!("could not read file `{}`", path))?;
    println!("file content: {}", content);
    Ok(())
}

This will print an error:

Error: could not read file `test.txt`

Caused by:
    No such file or directory (os error 2)

Struct std::io::Error

pub struct Error { /* fields omitted */ }

The error type for I/O operations of the Read, Write, Seek, and associated traits.

Errors mostly originate from the underlying OS, but custom instances of Error can be created with crafted error messages and a particular value of ErrorKind.

impl Error[src]

pub fn new<E>(kind: ErrorKind, error: E) -> Error where
    E: Into<Box<dyn Error + Send + Sync>>, 
[src]

Creates a new I/O error from a known kind of error as well as an arbitrary error payload.

This function is used to generically create I/O errors which do not originate from the OS itself. The error argument is an arbitrary payload which will be contained in this Error.

use std::io::{Error, ErrorKind};

// errors can be created from strings
let custom_error = Error::new(ErrorKind::Other, "oh no!");

// errors can also be created from other errors
let custom_error2 = Error::new(ErrorKind::Interrupted, custom_error);

pub fn last_os_error() -> Error[src]

Returns an error representing the last OS error which occurred.

This function reads the value of errno for the target platform (e.g. GetLastError on Windows) and will return a corresponding instance of Error for the error code.

use std::io::Error;

println!("last OS error: {:?}", Error::last_os_error());

pub fn from_raw_os_error(code: i32) -> Error[src]

Creates a new instance of an Error from a particular OS error code.

On Linux:

use std::io;

let error = io::Error::from_raw_os_error(22);
assert_eq!(error.kind(), io::ErrorKind::InvalidInput);

On Windows:

use std::io;

let error = io::Error::from_raw_os_error(10022);
assert_eq!(error.kind(), io::ErrorKind::InvalidInput);

pub fn raw_os_error(&self) -> Option<i32>[src]

Returns the OS error that this error represents (if any).

If this Error was constructed via last_os_error or from_raw_os_error, then this function will return Some, otherwise it will return None.

use std::io::{Error, ErrorKind};

fn print_os_error(err: &Error) {
    if let Some(raw_os_err) = err.raw_os_error() {
        println!("raw OS error: {:?}", raw_os_err);
    } else {
        println!("Not an OS error");
    }
}

fn main() {
    // Will print "raw OS error: ...".
    print_os_error(&Error::last_os_error());
    // Will print "Not an OS error".
    print_os_error(&Error::new(ErrorKind::Other, "oh no!"));
}

pub fn get_ref(&self) -> Option<&(dyn Error + Send + Sync + 'static)>[src]1.3.0

Returns a reference to the inner error wrapped by this error (if any).

If this Error was constructed via new then this function will return Some, otherwise it will return None.

use std::io::{Error, ErrorKind};

fn print_error(err: &Error) {
    if let Some(inner_err) = err.get_ref() {
        println!("Inner error: {:?}", inner_err);
    } else {
        println!("No inner error");
    }
}

fn main() {
    // Will print "No inner error".
    print_error(&Error::last_os_error());
    // Will print "Inner error: ...".
    print_error(&Error::new(ErrorKind::Other, "oh no!"));
}

pub fn get_mut(&mut self) -> Option<&mut (dyn Error + Send + Sync + 'static)>[src]1.3.0

Returns a mutable reference to the inner error wrapped by this error (if any).

If this Error was constructed via new then this function will return Some, otherwise it will return None.

use std::io::{Error, ErrorKind};
use std::{error, fmt};
use std::fmt::Display;

#[derive(Debug)]
struct MyError {
    v: String,
}

impl MyError {
    fn new() -> MyError {
        MyError {
            v: "oh no!".to_string()
        }
    }

    fn change_message(&mut self, new_message: &str) {
        self.v = new_message.to_string();
    }
}

impl error::Error for MyError {}

impl Display for MyError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "MyError: {}", &self.v)
    }
}

fn change_error(mut err: Error) -> Error {
    if let Some(inner_err) = err.get_mut() {
        inner_err.downcast_mut::<MyError>().unwrap().change_message("I've been changed!");
    }
    err
}

fn print_error(err: &Error) {
    if let Some(inner_err) = err.get_ref() {
        println!("Inner error: {}", inner_err);
    } else {
        println!("No inner error");
    }
}

fn main() {
    // Will print "No inner error".
    print_error(&change_error(Error::last_os_error()));
    // Will print "Inner error: ...".
    print_error(&change_error(Error::new(ErrorKind::Other, MyError::new())));
}

pub fn into_inner(self) -> Option<Box<dyn Error + Send + Sync>>[src]1.3.0

Consumes the Error, returning its inner error (if any).

If this Error was constructed via new then this function will return Some, otherwise it will return None.

use std::io::{Error, ErrorKind};

fn print_error(err: Error) {
    if let Some(inner_err) = err.into_inner() {
        println!("Inner error: {}", inner_err);
    } else {
        println!("No inner error");
    }
}

fn main() {
    // Will print "No inner error".
    print_error(Error::last_os_error());
    // Will print "Inner error: ...".
    print_error(Error::new(ErrorKind::Other, "oh no!"));
}

pub fn kind(&self) -> ErrorKind[src]

Returns the corresponding ErrorKind for this error.

use std::io::{Error, ErrorKind};

fn print_error(err: Error) {
    println!("{:?}", err.kind());
}

fn main() {
    // Will print "Other".
    print_error(Error::last_os_error());
    // Will print "AddrInUse".
    print_error(Error::new(ErrorKind::AddrInUse, "oh no!"));
}

impl Debug for Error[src]

impl Display for Error[src]

impl Error for Error[src]

fn description(&self) -> &str[src]

👎 Deprecated since 1.42.0: use the Display impl or to_string()

if let Err(e) = "xc".parse::<u32>() { // Print e itself, no need for description(). eprintln!("Error: {}", e); } Read more

fn cause(&self) -> Option<&dyn Error>[src]

👎 Deprecated since 1.33.0: replaced by Error::source, which can support downcasting

fn source(&self) -> Option<&(dyn Error + 'static)>[src]

The lower-level source of this error, if any. Read more

fn backtrace(&self) -> Option<&Backtrace>[src]

🔬 This is a nightly-only experimental API. (backtrace #53487)

Returns a stack backtrace, if available, of where this error occurred. Read more

impl From<ErrorKind> for Error[src]1.14.0

Intended for use for errors not exposed to the user, where allocating onto the heap (for normal construction via Error::new) is too costly.

fn from(kind: ErrorKind) -> Error[src]

Converts an ErrorKind into an Error.

This conversion allocates a new error with a simple representation of error kind.

use std::io::{Error, ErrorKind};

let not_found = ErrorKind::NotFound;
let error = Error::from(not_found);
assert_eq!("entity not found", format!("{}", error));

impl<W> From<IntoInnerError<W>> for Error[src]

impl From<NulError> for Error[src]

impl !RefUnwindSafe for Error

impl Send for Error

impl Sync for Error

impl Unpin for Error

impl !UnwindSafe for Error

impl<T> Any for T where
    T: 'static + ?Sized, 
[src]

impl<T> Borrow<T> for T where
    T: ?Sized, 
[src]

fn borrow(&self) -> &T

Notable traits for &'_ mut F

impl<'_, F> Future for &'_ mut F where
    F: Unpin + Future + ?Sized, 
    type Output = <F as Future>::Output;
impl<'_, I> Iterator for &'_ mut I where
    I: Iterator + ?Sized, 
    type Item = <I as Iterator>::Item;
impl<R: Read + ?Sized, '_> Read for &'_ mut R
impl<W: Write + ?Sized, '_> Write for &'_ mut W

[src]

Immutably borrows from an owned value. Read more

impl<T> BorrowMut<T> for T where
    T: ?Sized, 
[src]

fn borrow_mut(&mut self) -> &mut T

Notable traits for &'_ mut F

impl<'_, F> Future for &'_ mut F where
    F: Unpin + Future + ?Sized, 
    type Output = <F as Future>::Output;
impl<'_, I> Iterator for &'_ mut I where
    I: Iterator + ?Sized, 
    type Item = <I as Iterator>::Item;
impl<R: Read + ?Sized, '_> Read for &'_ mut R
impl<W: Write + ?Sized, '_> Write for &'_ mut W

[src]

Mutably borrows from an owned value. Read more

impl<T> From<T> for T[src]

fn from(t: T) -> T[src]

Performs the conversion.

impl<T, U> Into<U> for T where
    U: From<T>, 
[src]

fn into(self) -> U[src]

Performs the conversion.

impl<T> ToString for T where
    T: Display + ?Sized, 
[src]

impl<T, U> TryFrom<U> for T where
    U: Into<T>, 
[src]

impl<T, U> TryInto<U> for T where
    U: TryFrom<T>, 
[src]

Error handling in Rust is very different if you’re coming from other languages. In languages like Java, JS, Python etc, you usually throw exceptions and return successful values. In Rust, you return something called a Result.

The Result<T, E> type is an enum that has two variants — Ok(T) for successful value or Err(E) for error value:

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

Returning errors instead of throwing them is a paradigm shift in error handling. If you’re new to Rust, there will be some friction initially as it requires you to reason about errors in a different way.

In this post, I’ll go through some common error handling patterns so you gradually become familiar with how things are done in Rust:

  • Ignore the error
  • Terminate the program
  • Use a fallback value
  • Bubble up the error
  • Bubble up multiple errors
  • Match boxed errors
  • Libraries vs Applications
  • Create custom errors
  • Bubble up custom errors
  • Match custom errors

Ignore the error

Let’s start with the simplest scenario where we just ignore the error. This sounds careless but has a couple of legitimate use cases:

  • We’re prototyping our code and don’t want to spend time on error handling.
  • We’re confident that the error won’t occur.

Let’s say that we’re reading a file which we’re pretty sure would be present:

use std::fs;

fn main() {
  let content = fs::read_to_string("./Cargo.toml").unwrap();
  println!("{}", content)
}

Even though we know that the file would be present, the compiler has no way of knowing that. So we use unwrap to tell the compiler to trust us and return the value inside. If the read_to_string function returns an Ok() value, unwrap will get the contents of Ok and assign it to the content variable. If it returns an error, it will “panic”. Panic either terminates the program or exits the current thread.

Note that unwrap is used in quite a lot of Rust examples to skip error handling. This is mostly done for convenience and shouldn’t be used in real code as it is.

Terminate the program

Some errors cannot be handled or recovered from. In these cases, it’s better to fail fast by terminating the program.

Let’s use the same example as above — we’re reading a file which we’re sure to be present. Let’s imagine that, for this program, that file is absolutely important without which it won’t work properly. If for some reason, this file is absent, it’s better to terminate the program.

We can use unwrap as before or use expect — it’s same as unwrap but lets us add extra error message.

use std::fs;

fn main() {
  let content = fs::read_to_string("./Cargo.toml").expect("Can't read Cargo.toml");
  println!("{}", content)
}

See also: panic!

Use a fallback value

In some cases, you can handle the error by falling back to a default value.

For example, let’s say we’re writing a server and the port it listens to can be configured using an environment variable. If the environment variable is not set, accessing that value would result in an error. But we can easily handle that by falling back to a default value.

use std::env;

fn main() {
  let port = env::var("PORT").unwrap_or("3000".to_string());
  println!("{}", port);
}

Here, we’ve used a variation of unwrap called unwrap_or which lets us supply default values.

See also: unwrap_or_else, unwrap_or_default

Bubble up the error

When you don’t have enough context to handle the error, you can bubble up (propagate) the error to the caller function.

Here’s a contrived example which uses a webservice to get the current year:

use std::collections::HashMap;

fn main() {
  match get_current_date() {
    Ok(date) => println!("We've time travelled to {}!!", date),
    Err(e) => eprintln!("Oh noes, we don't know which era we're in! :( n  {}", e),
  }
}

fn get_current_date() -> Result<String, reqwest::Error> {
  let url = "https://postman-echo.com/time/object";
  let result = reqwest::blocking::get(url);

  let response = match result {
    Ok(res) => res,
    Err(err) => return Err(err),
  };

  let body = response.json::<HashMap<String, i32>>();

  let json = match body {
    Ok(json) => json,
    Err(err) => return Err(err),
  };

  let date = json["years"].to_string();

  Ok(date)
}

There are two function calls inside the get_current_date function (get and json) that return Result values. Since get_current_date doesn’t have context of what to do when they return errors, it uses pattern matching to propagate the errors to main.

Using pattern matching to handle multiple or nested errors can make your code “noisy”. Instead, we can rewrite the above code using the ? operator:

use std::collections::HashMap;

fn main() {
  match get_current_date() {
    Ok(date) => println!("We've time travelled to {}!!", date),
    Err(e) => eprintln!("Oh noes, we don't know which era we're in! :( n  {}", e),
  }
}

fn get_current_date() -> Result<String, reqwest::Error> {
  let url = "https://postman-echo.com/time/object";
  let res = reqwest::blocking::get(url)?.json::<HashMap<String, i32>>()?;
  let date = res["years"].to_string();

  Ok(date)
}

This looks much cleaner!

The ? operator is similar to unwrap but instead of panicking, it propagates the error to the calling function. One thing to keep in mind is that we can use the ? operator only for functions that return a Result or Option type.

Bubble up multiple errors

In the previous example, the get and json functions return a reqwest::Error error which we’ve propagated using the ? operator. But what if we’ve another function call that returned a different error value?

Let’s extend the previous example by returning a formatted date instead of the year:

+ use chrono::NaiveDate;
  use std::collections::HashMap;

  fn main() {
    match get_current_date() {
      Ok(date) => println!("We've time travelled to {}!!", date),
      Err(e) => eprintln!("Oh noes, we don't know which era we're in! :( n  {}", e),
    }
  }

  fn get_current_date() -> Result<String, reqwest::Error> {
    let url = "https://postman-echo.com/time/object";
    let res = reqwest::blocking::get(url)?.json::<HashMap<String, i32>>()?;
-   let date = res["years"].to_string();
+   let formatted_date = format!("{}-{}-{}", res["years"], res["months"] + 1, res["date"]);
+   let parsed_date = NaiveDate::parse_from_str(formatted_date.as_str(), "%Y-%m-%d")?;
+   let date = parsed_date.format("%Y %B %d").to_string();

    Ok(date)
  }

The above code won’t compile as parse_from_str returns a chrono::format::ParseError error and not reqwest::Error.

We can fix this by Boxing the errors:

  use chrono::NaiveDate;
  use std::collections::HashMap;

  fn main() {
    match get_current_date() {
      Ok(date) => println!("We've time travelled to {}!!", date),
      Err(e) => eprintln!("Oh noes, we don't know which era we're in! :( n  {}", e),
    }
  }

- fn get_current_date() -> Result<String, reqwest::Error> {
+ fn get_current_date() -> Result<String, Box<dyn std::error::Error>> {
    let url = "https://postman-echo.com/time/object";
    let res = reqwest::blocking::get(url)?.json::<HashMap<String, i32>>()?;

    let formatted_date = format!("{}-{}-{}", res["years"], res["months"] + 1, res["date"]);
    let parsed_date = NaiveDate::parse_from_str(formatted_date.as_str(), "%Y-%m-%d")?;
    let date = parsed_date.format("%Y %B %d").to_string();

    Ok(date)
  }

Returning a trait object Box<dyn std::error::Error> is very convenient when we want to return multiple errors!

See also: anyhow, eyre

Match boxed errors

So far, we’ve only printed the errors in the main function but not handled them. If we want to handle and recover from boxed errors, we need to “downcast” them:

  use chrono::NaiveDate;
  use std::collections::HashMap;

  fn main() {
    match get_current_date() {
      Ok(date) => println!("We've time travelled to {}!!", date),
-     Err(e) => eprintln!("Oh noes, we don't know which era we're in! :( n  {}", e),
+     Err(e) => {
+       eprintln!("Oh noes, we don't know which era we're in! :(");
+       if let Some(err) = e.downcast_ref::<reqwest::Error>() {
+         eprintln!("Request Error: {}", err)
+       } else if let Some(err) = e.downcast_ref::<chrono::format::ParseError>() {
+         eprintln!("Parse Error: {}", err)
+       }
+     }
    }
  }

  fn get_current_date() -> Result<String, Box<dyn std::error::Error>> {
    let url = "https://postman-echo.com/time/object";
    let res = reqwest::blocking::get(url)?.json::<HashMap<String, i32>>()?;

    let formatted_date = format!("{}-{}-{}", res["years"], res["months"] + 1, res["date"]);
    let parsed_date = NaiveDate::parse_from_str(formatted_date.as_str(), "%Y-%m-%d")?;
    let date = parsed_date.format("%Y %B %d").to_string();

    Ok(date)
  }

Notice how we need to be aware of the implementation details (different errors inside) of get_current_date to be able to downcast them inside main.

See also: downcast, downcast_mut

Applications vs Libraries

As mentioned previously, the downside to boxed errors is that if we want to handle the underlying errors, we need to be aware of the implementation details. When we return something as Box<dyn std::error::Error>, the concrete type information is erased. To handle the different errors in different ways, we need to downcast them to concrete types and this casting can fail at runtime.

However, saying something is a “downside” is not very useful without context. A good rule of thumb is to question whether the code you’re writing is an “application” or a “library”:

Application

  • The code you’re writing would be used by end users.
  • Most errors generated by application code won’t be handled but instead logged or reported to the user.
  • It’s okay to use boxed errors.

Library

  • The code you’re writing would be consumed by other code. A “library” can be open source crates, internal libraries etc
  • Errors are part of your library’s API, so your consumers know what errors to expect and recover from.
  • Errors from your library are often handled by your consumers so they need to be structured and easy to perform exhaustive match on.
  • If you return boxed errors, then your consumers need to be aware of the errors created by your code, your dependencies, and so on!
  • Instead of boxed errors, we can return custom errors.

Create custom errors

For library code, we can convert all the errors to our own custom error and propagate them instead of boxed errors. In our example, we currently have two errors — reqwest::Error and chrono::format::ParseError. We can convert them to MyCustomError::HttpError and MyCustomError::ParseError respectively.

Let’s start by creating an enum to hold our two error variants:

// error.rs

pub enum MyCustomError {
  HttpError,
  ParseError,
}

The Error trait requires us to implement the Debug and Display traits:

// error.rs

use std::fmt;

#[derive(Debug)]
pub enum MyCustomError {
  HttpError,
  ParseError,
}

impl std::error::Error for MyCustomError {}

impl fmt::Display for MyCustomError {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    match self {
      MyCustomError::HttpError => write!(f, "HTTP Error"),
      MyCustomError::ParseError => write!(f, "Parse Error"),
    }
  }
}

We’ve created our own custom error!

This is obviously a simple example as the error variants don’t contain much information about the error. But this should be sufficient as a starting point for creating more complex and realistic custom errors. Here are some real life examples: ripgrep, reqwest, csv and serde_json

See also: thiserror, snafu

Bubble up custom errors

Let’s update our code to return the custom errors we just created:

  // main.rs

+ mod error;

  use chrono::NaiveDate;
+ use error::MyCustomError;
  use std::collections::HashMap;

  fn main() {
    // skipped, will get back later
  }

- fn get_current_date() -> Result<String, Box<dyn std::error::Error>> {
+ fn get_current_date() -> Result<String, MyCustomError> {
    let url = "https://postman-echo.com/time/object";
-   let res = reqwest::blocking::get(url)?.json::<HashMap<String, i32>>()?;
+   let res = reqwest::blocking::get(url)
+     .map_err(|_| MyCustomError::HttpError)?
+     .json::<HashMap<String, i32>>()
+     .map_err(|_| MyCustomError::HttpError)?;

    let formatted_date = format!("{}-{}-{}", res["years"], res["months"] + 1, res["date"]);
-   let parsed_date = NaiveDate::parse_from_str(formatted_date.as_str(), "%Y-%m-%d")?;
+   let parsed_date = NaiveDate::parse_from_str(formatted_date.as_str(), "%Y-%m-%d")
+     .map_err(|_| MyCustomError::ParseError)?;
    let date = parsed_date.format("%Y %B %d").to_string();

    Ok(date)
  }

Notice how we’re using map_err to convert the error from one type to another type.

But things got verbose as a result — our function is littered with these map_err calls. We can implement the From trait to automatically coerce the error types when we use the ? operator:

  // error.rs

  use std::fmt;

  #[derive(Debug)]
  pub enum MyCustomError {
    HttpError,
    ParseError,
  }

  impl std::error::Error for MyCustomError {}

  impl fmt::Display for MyCustomError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
      match self {
        MyCustomError::HttpError => write!(f, "HTTP Error"),
        MyCustomError::ParseError => write!(f, "Parse Error"),
      }
    }
  }

+ impl From<reqwest::Error> for MyCustomError {
+   fn from(_: reqwest::Error) -> Self {
+     MyCustomError::HttpError
+   }
+ }

+ impl From<chrono::format::ParseError> for MyCustomError {
+   fn from(_: chrono::format::ParseError) -> Self {
+     MyCustomError::ParseError
+   }
+ }
  // main.rs

  mod error;

  use chrono::NaiveDate;
  use error::MyCustomError;
  use std::collections::HashMap;

  fn main() {
    // skipped, will get back later
  }

  fn get_current_date() -> Result<String, MyCustomError> {
    let url = "https://postman-echo.com/time/object";
-   let res = reqwest::blocking::get(url)
-     .map_err(|_| MyCustomError::HttpError)?
-     .json::<HashMap<String, i32>>()
-     .map_err(|_| MyCustomError::HttpError)?;
+   let res = reqwest::blocking::get(url)?.json::<HashMap<String, i32>>()?;

    let formatted_date = format!("{}-{}-{}", res["years"], res["months"] + 1, res["date"]);
-   let parsed_date = NaiveDate::parse_from_str(formatted_date.as_str(), "%Y-%m-%d")
-     .map_err(|_| MyCustomError::ParseError)?;
+   let parsed_date = NaiveDate::parse_from_str(formatted_date.as_str(), "%Y-%m-%d")?;
    let date = parsed_date.format("%Y %B %d").to_string();

    Ok(date)
  }

We’ve removed map_err and the code looks much cleaner!

However, From trait is not magic and there are times when we need to use map_err. In the above example, we’ve moved the type conversion from inside the get_current_date function to the From<X> for MyCustomError implementation. This works well when the information needed to convert from one error to MyCustomError can be obtained from the original error object. If not, we need to use map_err inside get_current_date.

Match custom errors

We’ve ignored the changes in main until now, here’s how we can handle the custom errors:

  // main.rs

  mod error;

  use chrono::NaiveDate;
  use error::MyCustomError;
  use std::collections::HashMap;

  fn main() {
    match get_current_date() {
      Ok(date) => println!("We've time travelled to {}!!", date),
      Err(e) => {
        eprintln!("Oh noes, we don't know which era we're in! :(");
-       if let Some(err) = e.downcast_ref::<reqwest::Error>() {
-         eprintln!("Request Error: {}", err)
-       } else if let Some(err) = e.downcast_ref::<chrono::format::ParseError>() {
-         eprintln!("Parse Error: {}", err)
-       }
+       match e {
+         MyCustomError::HttpError => eprintln!("Request Error: {}", e),
+         MyCustomError::ParseError => eprintln!("Parse Error: {}", e),
+       }
      }
    }
  }

  fn get_current_date() -> Result<String, MyCustomError> {
    let url = "https://postman-echo.com/time/object";
    let res = reqwest::blocking::get(url)?.json::<HashMap<String, i32>>()?;

    let formatted_date = format!("{}-{}-{}", res["years"], res["months"] + 1, res["date"]);
    let parsed_date = NaiveDate::parse_from_str(formatted_date.as_str(), "%Y-%m-%d")?;
    let date = parsed_date.format("%Y %B %d").to_string();

    Ok(date)
  }

Notice how unlike boxed errors, we can actually match on the variants inside MyCustomError enum.

Conclusion

Thanks for reading! I hope this post was helpful in introducing the basics of error handling in Rust. I’ve added the examples to a repo in GitHub which you can use for practice. If you’ve more questions, please contact me at sheshbabu [at] gmail.com. Feel free to follow me in Twitter for more posts like this :)

Introduction

Rust uses Result<T, E> values to indicate recoverable errors during execution. Unrecoverable errors cause Panics which is a topic of its own.

Common Result methods

use std::io::{Read, Result as IoResult};
use std::fs::File;

struct Config(u8);

fn read_config() -> IoResult<String> {
    let mut s = String::new();
    let mut file = File::open(&get_local_config_path())
        // or_else closure is invoked if Result is Err.
        .or_else(|_| File::open(&get_global_config_path()))?;
    // Note: In `or_else`, the closure should return a Result with a matching
    //       Ok type, whereas in `and_then`, the returned Result should have a
    //       matching Err type.
    let _ = file.read_to_string(&mut s)?;
    Ok(s)
}

struct ParseError;

fn parse_config(conf_str: String) -> Result<Config, ParseError> {
    // Parse the config string...
    if conf_str.starts_with("bananas") {
        Err(ParseError)
    } else {
        Ok(Config(42))
    }
}

fn run() -> Result<(), String> {
    // Note: The error type of this function is String. We use map_err below to
    //       make the error values into String type
    let conf_str = read_config()
        .map_err(|e| format!("Failed to read config file: {}", e))?;
    // Note: Instead of using `?` above, we can use `and_then` to wrap the let
    //       expression below.
    let conf_val = parse_config(conf_str)
        .map(|Config(v)| v / 2) // map can be used to map just the Ok value
        .map_err(|_| "Failed to parse the config string!".to_string())?;

    // Run...

    Ok(())
}

fn main() {
    match run() {
        Ok(_) => println!("Bye!"),
        Err(e) => println!("Error: {}", e),
    }
}

fn get_local_config_path() -> String {
    let user_config_prefix = "/home/user/.config";
    // code to get the user config directory
    format!("{}/my_app.rc", user_config_prefix)
}

fn get_global_config_path() -> String {
    let global_config_prefix = "/etc";
    // code to get the global config directory
    format!("{}/my_app.rc", global_config_prefix)
}

If the config files don’t exist, this outputs:

Error: Failed to read config file: No such file or directory (os error 2)

If parsing failed, this outputs:

Error: Failed to parse the config string!

Note: As the project grows, it will get cumbersome to handle errors with these basic methods (docs) without losing information about the origin and propagation path of errors. Also, it is definitely a bad practice to convert errors into strings prematurely in order to handle multiple error types as shown above. A much better way is to use the crate error-chain.

Custom Error Types

use std::error::Error;
use std::fmt;
use std::convert::From;
use std::io::Error as IoError;
use std::str::Utf8Error;

#[derive(Debug)] // Allow the use of "{:?}" format specifier
enum CustomError {
    Io(IoError),
    Utf8(Utf8Error),
    Other,
}

// Allow the use of "{}" format specifier
impl fmt::Display for CustomError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            CustomError::Io(ref cause) => write!(f, "I/O Error: {}", cause),
            CustomError::Utf8(ref cause) => write!(f, "UTF-8 Error: {}", cause),
            CustomError::Other => write!(f, "Unknown error!"),
        }
    }
}

// Allow this type to be treated like an error
impl Error for CustomError {
    fn description(&self) -> &str {
        match *self {
            CustomError::Io(ref cause) => cause.description(),
            CustomError::Utf8(ref cause) => cause.description(),
            CustomError::Other => "Unknown error!",
        }
    }

    fn cause(&self) -> Option<&Error> {
        match *self {
            CustomError::Io(ref cause) => Some(cause),
            CustomError::Utf8(ref cause) => Some(cause),
            CustomError::Other => None,
        }
    }
}

// Support converting system errors into our custom error.
// This trait is used in `try!`.
impl From<IoError> for CustomError {
    fn from(cause: IoError) -> CustomError {
        CustomError::Io(cause)
    }
}
impl From<Utf8Error> for CustomError {
    fn from(cause: Utf8Error) -> CustomError {
        CustomError::Utf8(cause)
    }
}

Iterating through causes

It is often useful for debugging purposes to find the root cause of an error. In order to examine an error value that implements std::error::Error:

use std::error::Error;

let orig_error = call_returning_error();

// Use an Option<&Error>. This is the return type of Error.cause().
let mut err = Some(&orig_error as &Error);

// Print each error's cause until the cause is None.
while let Some(e) = err {
    println!("{}", e);
    err = e.cause();
}

Basic Error Reporting and Handling

Result<T, E> is an enum type which has two variants: Ok(T) indicating successful execution with meaningful result of type T, and Err(E) indicating occurrence of an unexpected error during execution, described by a value of type E.

enum DateError {
    InvalidDay,
    InvalidMonth,
}

struct Date {
    day: u8,
    month: u8,
    year: i16,
}

fn validate(date: &Date) -> Result<(), DateError> {
    if date.month < 1 || date.month > 12 {
        Err(DateError::InvalidMonth)
    } else if date.day < 1 || date.day > 31 {
        Err(DateError::InvalidDay)
    } else {
        Ok(())
    }
}

fn add_days(date: Date, days: i32) -> Result<Date, DateError> {
    validate(&date)?; // notice `?` -- returns early on error
    // the date logic ...
    Ok(date)
}

Also see docs for more details on ? operator.

Standard library contains an Error trait which all error types are recommended to implement. An example implementation is given below.

use std::error::Error;
use std::fmt;

#[derive(Debug)]
enum DateError {
    InvalidDay(u8),
    InvalidMonth(u8),
}

impl fmt::Display for DateError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            &DateError::InvalidDay(day) => write!(f, "Day {} is outside range!", day),
            &DateError::InvalidMonth(month) => write!(f, "Month {} is outside range!", month),
        }
    }
}

impl Error for DateError {
    fn description(&self) -> &str {
        match self {
            &DateError::InvalidDay(_) => "Day is outside range!",
            &DateError::InvalidMonth(_) => "Month is outside range!",
        }
    }

    // cause method returns None by default
}

Note: Generally, Option<T> should not be used for reporting errors. Option<T> indicates an expected possibility of non-existence of a value and a single straightforward reason for it. In contrast, Result<T, E> is used to report unexpected errors during execution, and especially when there are multiple modes of failures to distinguish between them. Furthermore, Result<T, E> is only used as return values. (An old discussion.)

Понравилась статья? Поделить с друзьями:
  • Rust error log
  • Rust error handling
  • Rust error code 10011
  • Rust error anyhow
  • Rust error allocating memory