Rust error anyhow

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

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§
source

Available on crate feature std only.

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

Available on nightly or crate feature backtrace only.

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

Available on crate feature std only.

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

Available on crate feature std only.

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.

source§
source§

Converts this type into a shared reference of the (usually inferred) input type.

source§
source§

Converts this type into a shared reference of the (usually inferred) input type.

source§
source§

Available on crate feature std only.

§

The resulting type after dereferencing.

source§

Dereferences the value.

source§

Available on crate feature std only.

source§

Mutably dereferences the value.

source§
source§
source§

Available on crate feature std only.

source§

Converts to this type from the input type.

source§
source§

Converts to this type from the input type.

source§
source§

Converts to this type from the input type.

source§
source§

Converts to this type from the input type.

source§
source§

🔬This is a nightly-only experimental API. (provide_any)

Data providers should implement this method to provide all values they are able to
provide by using demand. Read more

§
§
§
§
§

Anyhow ¯_(°ペ)_/¯

github
crates.io
docs.rs
build status

This library provides anyhow::Error, a trait object based error type
for easy idiomatic error handling in Rust applications.

[dependencies]
anyhow = "1.0"

Compiler support: requires rustc 1.39+

Details

  • Use Result<T, anyhow::Error>, or equivalently anyhow::Result<T>, as the
    return type of any fallible function.

    Within the function, use ? to easily propagate any error that implements the
    std::error::Error trait.

    use anyhow::Result;
    
    fn get_cluster_info() -> Result<ClusterMap> {
        let config = std::fs::read_to_string("cluster.json")?;
        let map: ClusterMap = serde_json::from_str(&config)?;
        Ok(map)
    }
  • Attach context to help the person troubleshooting the error understand where
    things went wrong. A low-level error like «No such file or directory» can be
    annoying to debug without more context about what higher level step the
    application was in the middle of.

    use anyhow::{Context, Result};
    
    fn main() -> Result<()> {
        ...
        it.detach().context("Failed to detach the important thing")?;
    
        let content = std::fs::read(path)
            .with_context(|| format!("Failed to read instrs from {}", path))?;
        ...
    }
    Error: Failed to read instrs from ./path/to/instrs.json
    
    Caused by:
        No such file or directory (os error 2)
  • Downcasting is supported and can be by value, by shared reference, or by
    mutable reference as needed.

    // 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),
    }
  • If using the nightly channel, or stable with features = ["backtrace"], a
    backtrace is captured and printed with the error if the underlying error type
    does not already provide its own. In order to see backtraces, they must be
    enabled through the environment variables described in std::backtrace:

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

    The tracking issue for this feature is rust-lang/rust#53487.

  • Anyhow works with any error type that has an impl of std::error::Error,
    including ones defined in your crate. We do not bundle a derive(Error) macro
    but you can write the impls yourself or use a standalone macro like
    thiserror.

    use thiserror::Error;
    
    #[derive(Error, Debug)]
    pub enum FormatError {
        #[error("Invalid header (expected {expected:?}, got {found:?})")]
        InvalidHeader {
            expected: String,
            found: String,
        },
        #[error("Missing attribute: {0}")]
        MissingAttribute(String),
    }
  • One-off error messages can be constructed using the anyhow! macro, which
    supports string interpolation and produces an anyhow::Error.

    return Err(anyhow!("Missing attribute: {}", missing));

    A bail! macro is provided as a shorthand for the same early return.

    bail!("Missing attribute: {}", missing);

No-std support

In no_std mode, the same API is almost all available and works the same way. To
depend on Anyhow in no_std mode, disable our default enabled «std» feature in
Cargo.toml. A global allocator is required.

[dependencies]
anyhow = { version = "1.0", default-features = false }

Since the ?-based error conversions would normally rely on the
std::error::Error trait which is only available through std, no_std mode will
require an explicit .map_err(Error::msg) when working with a non-Anyhow error
type inside a function that returns Anyhow’s error type.

Comparison to failure

The anyhow::Error type works something like failure::Error, but unlike
failure ours is built around the standard library’s std::error::Error trait
rather than a separate trait failure::Fail. The standard library has adopted
the necessary improvements for this to be possible as part of RFC 2504.

Comparison to thiserror

Use Anyhow if you don’t care what error type your functions return, you just
want it to be easy. This is common in application code. Use thiserror if you
are a library that wants to design your own dedicated error type(s) so that on
failures the caller gets exactly the information that you choose.

License


Licensed under either of Apache License, Version
2.0 or MIT license at your option.



Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in this crate by you, as defined in the Apache-2.0 license, shall
be dual licensed as above, without any additional terms or conditions.

Cover image for How to Handle Errors in Rust: A Comprehensive Guide

Nathan

Nathan

Posted on Dec 2, 2022

• Updated on Dec 15, 2022

Rust community constantly discusses about error handling.. In this article I will try to explain what is it then why, and how we should use it.

Purpose of Error Handling

Error handling is a process that helps to identify, debug, and resolve errors that occur during the execution of a program.
It helps to ensure the smooth functioning of the program by preventing errors from occurring and allows the program to continue running in an optimal state.
Error handling also allows users to be informed of any problems that may arise and take corrective action to prevent the errors from happening again in the future.

What is a Result?

Result is a built-in enum in the Rust standard library.
It has two variants Ok(T) and Err(E).

Image description

Result should be used as a return type for a function that can encounter error situations.
Ok value is return in case of success or an Err value in case of an error.

Implementation of Result in a function.

Image description

What is Error Handling

Sometimes we are using functions that can fail, for example calling an endpoint from an API or searching a file. These type of function can encounter errors (in our case the API is not reachable or the file is not existing).
There are similar scenarios where we are using Error Handling.

Image description

Explained Step by Step

  • A Result is the result of the read username from file function.
    It follows that the function’s returned value will either be an Ok that contains a String or an Err that contains an instance of io::Error.

There is a call to «File::open» inside of read username from file, which returns a Result type.

  • It can return an Ok
  • It can return an Err

Then the code calls a match to check the result of the function and return the value inside the ok in the case the function was successful or return the Error value.

In the second function read_to_string, the same principle is applied, but in this case we did not use the keyword return as you can see, and we finally return either an OK or an Err.

So you may ask: On every result type I have to write all these Match block?

So hopefully there is a shortcut :)

Image description

What is the Question Mark- Propagation Error?

According to the rust lang book:

The question mark operator (?) unwraps valid values or returns erroneous values, propagating them to the calling function. It is a unary postfix operator that can only be applied to the types Result and Option.

Let’s me explain it.

Question mark (?) in Rust is used to indicate a Result type. It is used to return an error value if the operation cannot be completed.
For example, in our function that reads a file, it can return a Result type, where the question mark indicates that an error might be returned if the file cannot be read, or in the other hand the final result.
In other words, used to short-circuit a chain of computations and return early if a condition is not met.

fn read_username_from_file() -> Result<String, io::Error> {
    let mut f = File::open("username.txt")?;
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
}

Enter fullscreen mode

Exit fullscreen mode

Every time you see a ?, that’s a possible early return from the function in case of Error, else , f will hold the file handle the Ok contained and execution of the function continues (similary to unwrap function).

Why use crates for Handle errors?

Standard library does not provide all solutions for Error Handling..
In fact, different errors may be returned by the same function, making it increasingly difficult to handle them precisely.
Personal anecdote, in our company we developed Cherrybomb an API security tool written in Rust, and we need to re-write a good part of it to have a better errors handling.

For example:

Image description

Or the same message error can be displayed multiples times.

Image description

This is why we need to define our own custom Error enum.

Image description

Then our function will look like:

Image description

Customize Errors

Thiserror focuses on creating structured errorsand has only one trait that can be used to define new errors:

Thiserror is an error-handling library for Rust that provides a powerful yet concise syntax to create custom error types.

In the cargo toml:

[dependencies]
thiserror = "1.0"

It allows developers to create custom error types and handlers without having to write a lot of boilerplate code.

Thank to thiserror crate, we can customize our error messages.

Image description

It also provides features to automatically convert between custom error types and the standard error type. We will see it in the next Chapter with Dynamic Error.

  • Create new errors through #[derive(Error)].
  • Enums, structs with named fields, tuple structs, and unit structs are all possible.
  • A Display impl is generated for your error if you provide #[error(«…»)] messages on the struct or each variant of your enum and support string interpolation.

Example taken from docs.rs:

Image description

Dealing Dynamic Errors handling

If you want to be able to use?, your Error type must implement the From trait for the error types of your dependencies. Your program or library may use many dependencies, each of which has its own error you have two different structs of custom error, and we call a function that return one specific type.
For example:
Image description

So when we call our main function that return a ErrorA type, we encounter the following error:

Image description

So one of the solution is to implement the trait From<ErrorB> for the struct ErrorA.

Our code looks like this now:

Image description

Another solution to this problem is to return dynamic errors.
To handle dynamic errors in Rust, in the case of an Err value, you can use the box operator to return the error as a Box (a trait object of the Error trait). This allows the error type to be determined at runtime, rather than at compile time, making it easier to work with errors of different types.

The Box can then be used to store any type of Error, including those from external libraries or custom errors. The Box can then be used to propagate the Error up the call stack, allowing for appropriate handling of the error at each stage.

Image description

Thiserror crate

In order to have a code clearer and soft let’s use thiserror crate.
The thiserror crate can help handle dynamic errors in Rust by allowing the user to define custom error types. It does this through the #[derive(thiserror::Error)] macro. This macro allows the user to define a custom error type with a specific set of parameters, such as an error code, a message, and the source of the error. The user can then use this error type to return an appropriate error value in the event of a dynamic error. Additionally, the thiserror crate also provides several helpful methods, such as display_chain, which can be used to chain together multiple errors into a single error chain.
In the following we have created our error type ErrorB , then we used the From trait to convert from ErrorB errors into our custom ErrorA error type. If a dynamic error occurs, you can create a new instance of your error type and return it to the caller. See function returns_error_a() in line 13.

Image description

Anyhow crate

anyhow was written by the same author, dtolnay, and released in the same week as thiserror.
The anyhow can be used to return errors of any type that implement the std::error::Error trait and will display a nicely formatted error message if the program crashes.
The most common way to use the crate is to wrap your code in a Result type. This type is an alias for the std::result::Result<T, E> type, and it allows you to handle success or failure cases separately.

Image description

When an error occurs,for example you can use the context() method to provide more information about the error, or use the with_chain() method to chain multiple errors together.
The anyhow crate provides several convenient macros to simplify the process of constructing and handling errors. These macros include the bail!() and try_with_context!()macros.
The former can be used to quickly construct an error value, while the latter can be used to wrap a function call and automatically handle any errors that occur.

Comparison

The main difference between anyhow and the Thiserror crate in Rust is the way in which errors are handled. Anyhow allows for error handling using any type that implements the Error trait, whereas Thiserror requires you to explicitly define the error types using macros.

Anyhow is an error-handling library for Rust that provides an easy way to convert errors into a uniform type. It allows to write concise and powerful error-handling code by automatically converting many different types of errors into a single, common type.

In conclusion,in Cherrybomb we choose to combining the two, in order to create a custom error type with thiserror and managed it by the anyhow crate.

Introduction

In this article, I will discuss error handling in Rust 🦀. I try to explain the differences between recoverable and unrecoverable errors, and how to handle them properly in your code.

At the end of this article, I will also take a quick lookinto two popular crates for error handling in Rust 🦀: anyhow and thiserror.

The Panic Macro and Unrecoverable Errors

A Panic is an exception that a Rust 🦀 program can throw. It stops all execution in the current thread. Panic, will return a short description of the error and the location of the panic in the source code.

Let’s look at an example:

fn main() {
    println!("Hello, world!");
    panic!("oh no!");
}

This will print Hello, world! and then panic with the message oh no! and the location of the panic in the source code.

If your running this code in a terminal, you will see the following output:

cargo run                                                
   Compiling rust-error v0.1.0 (/Users/dirien/Tools/repos/quick-bites/rust-error)
    Finished dev [unoptimized + debuginfo] target(s) in 0.61s
     Running `target/debug/rust-error`
Hello, world!
thread 'main' panicked at 'oh no!', src/main.rs:3:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

The message gives us also a hint on how to display a backtrace. If you run the code with the environment variable RUST_BACKTRACE=1 you will get a list of all the functions leading up to the panic.

RUST_BACKTRACE=1 cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/rust-error`
Hello, world!
thread 'main' panicked at 'oh no!', src/main.rs:3:5
stack backtrace:
   0: rust_begin_unwind
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/std/src/panicking.rs:584:5
   1: core::panicking::panic_fmt
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/panicking.rs:142:14
   2: rust_error::main
             at ./src/main.rs:3:5
   3: core::ops::function::FnOnce::call_once
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/ops/function.rs:248:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

In this case, the backtrace is not very useful, because the panic is in the main function.

Let’s look at a different example, which is extremely contrived, but for demonstration purposes, it will do.

fn a() {
    b();
}

fn b() {
    c("engin");
}

fn c(name: &str) {
    if name == "engin" {
        panic!("Dont pass engin");
    }
}

fn main() {
    a();
}

We have three functions a, b and c. The main function calls a. a calls b and b calls c. c takes a string as an argument and panics if the string is engin.

cargo run
   Compiling rust-error v0.1.0 (/Users/dirien/Tools/repos/quick-bites/rust-error)
    Finished dev [unoptimized + debuginfo] target(s) in 0.14s
     Running `target/debug/rust-error`
thread 'main' panicked at 'Dont pass engin', src/main.rs:11:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

This error is not very useful. We can see that the panic happened in c, but we don’t know which function called c.

If we run the code with the environment variable RUST_BACKTRACE=1 we get the following output:

RUST_BACKTRACE=1 cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/rust-error`
thread 'main' panicked at 'Dont pass engin', src/main.rs:11:9
stack backtrace:
   0: rust_begin_unwind
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/std/src/panicking.rs:584:5
   1: core::panicking::panic_fmt
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/panicking.rs:142:14
   2: rust_error::c
             at ./src/main.rs:11:9
   3: rust_error::b
             at ./src/main.rs:6:5
   4: rust_error::a
             at ./src/main.rs:2:5
   5: rust_error::main
             at ./src/main.rs:16:5
   6: core::ops::function::FnOnce::call_once
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/ops/function.rs:248:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

This is much better. We can see that the panic happened in c, and we can see the call stack leading up to the panic. We see that c was called by b, which was called by a, which was called by main. So let’s change the code in b to call c with a different name.

fn b() {
    c("dirien");
}

Now the code compiles and runs without any problems.

Recoverable Errors

A recoverable error is an error that can be handled by the code. For example, if we try to open a file that does not exist, we can handle the error and print a message to the user or create the file instead of crashing the program.

For this case, we can use the Result type. The Result type is an enum with two variants: Ok and Err. The Ok variant indicates that the operation was successful and stores a generic value. The Err variant indicates that the operation failed and stores an error value.

Like the Option type, the Result type is defined in the standard library, and we need to bring it into scope.

Let’s look at an example. We will try to open a file and read the contents of the file.

fn main() {
    let f = File::open("hello.txt");
}

Here we need to check the result of the open function. If the file is opened successfully, we can read the contents of the file. If the file is not opened successfully, we can print an error message to the user.

To check the result of the open function, we can use the match expression. The match expression is similar to the if expression, but it can handle more than two cases. We’re also shadowing the f variable and setting it to the match expression.

fn main() {
    let f = File::open("hello.txt");

    let f = match f {
        Ok(file) => file,
        Err(error) => panic!("There was a problem opening the file: {:?}", error),
    };
}

If the open function returns Ok, we store the file handle in the f variable. If the open function returns Err, we panic and print the error message.

Let us run the code and see what happens.

cargo run
warning: `rust-error` (bin "rust-error") generated 1 warning
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/rust-error`
thread 'main' panicked at 'There was a problem opening the file: Os { code: 2, kind: NotFound, message: "No such file or directory" }', src/main.rs:7:23
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

We get a panic, but the error message is much more useful. We can see that the error is Os { code: 2, kind: NotFound, message: "No such file or directory" }. This error makes sense because we are trying to open a file that does not exist.

Now let’s enhance the code instead of panicking, we will create the file if it does not exist. First, we will bring the ErrorKind enum into scope.

use std::fs::File;
use std::io::ErrorKind;
...

Then we will use the match expression to check the error kind. If the error kind is NotFound, we will create the file. But the creation of the file can also fail, so we will use the match expression again to check the result of the create function. If the create function returns Ok, we will return the file handle. If the create function returns Err, we will panic.

The last part is to use other_error to handle all other errors that are not ErrorKind::NotFound.

fn main() {
    let f = File::open("hello.txt");

    let f = match f {
        Ok(file) => file,
        Err(error) => match error.kind() {
            ErrorKind::NotFound => match File::create("hello.txt") {
                Ok(fc) => fc,
                Err(e) => panic!("Problem creating the file: {:?}", e),
            },
            other_error => panic!("There was a problem opening the file: {:?}", other_error),
        },
    };
}

Now when we run the code, we can see that no panic happens. And if we check the directory, we can see that the file was created.

cargo run
   Compiling rust-error v0.1.0 (/Users/dirien/Tools/repos/quick-bites/rust-error)
    Finished dev [unoptimized + debuginfo] target(s) in 0.62s
     Running `target/debug/rust-error`

But this code is not very readable. We have a lot of match expressions. A better way to handle this is to use closures. We will use closures to handle the Ok and Err variants of the Result type.

When we attempt to open a file, we will use the unwrap_or_else method which gives us back the file or calls the anonymous function or closure that we pass the error to. Inside the closure, we will check the error kind. If the error is NotFound then we attempt to create the file called the unwrap_or_else method again. This gives us back the file if the calls succeed. Note that we don’t have a semicolon at the end which means this is an expression and not a statement. In the failure case, we have another closure that will just panic.

fn main() {
    let f = File::open("hello.txt").unwrap_or_else(|error| {
        if error.kind() == ErrorKind::NotFound {
            File::create("hello.txt").unwrap_or_else(|error| {
                panic!("Problem creating the file: {:?}", error);
            })
        } else {
            panic!("There was a problem opening the file: {:?}", error);
        }
    });
}

Now we going to rewrite the code again to use the unwrap and expect methods. The unwrap method is a shortcut method that is implemented on Result types. If the Result is Ok, the unwrap method will return the value inside the Ok. If the Result is Err, the unwrap method will call the panic! macro for us.

fn main() {
    let f = File::open("hello.txt").unwrap();
}

When we run the code, we get the same error as before.

cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.10s
     Running `target/debug/rust-error`
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Os { code: 2, kind: NotFound, message: "No such file or directory" }', src/main.rs:4:37
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

The expect method is similar to the unwrap method, but we can pass a custom error message to the expect method. This error message will be printed when the Result is Err.

fn main() {
    let f = File::open("hello.txt").expect("OMG! I cant open the file!");
}

When we run the code, we can see our custom error message.

cargo run
   Compiling rust-error v0.1.0 (/Users/dirien/Tools/repos/quick-bites/rust-error)
    Finished dev [unoptimized + debuginfo] target(s) in 0.10s
     Running `target/debug/rust-error`
thread 'main' panicked at 'OMG! I cant open the file!: Os { code: 2, kind: NotFound, message: "No such file or directory" }', src/main.rs:4:37
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

How to propagate errors

In the previous section, we saw how to handle errors. But what if we want to propagate the error to the caller of our function? This gives the caller the ability to handle the error.

Let’s say we want to read the contents of a file. We will create a function that reads username from a file. The function will return a Result type. The Result type will contain a String on success and the io::Error on error.

If the file does not exist, we will return the error. If the file exists, we will try to read the contents of the file. If this is not successful, we will return the error. If the read is successful, we will return the username.

use std::fs::File;
use std::io::{self, Read};

fn read_username_from_file() -> Result<String, io::Error> {
    let username_file_result = File::open("hello.txt");

    let mut username_file = match username_file_result {
        Ok(file) => file,
        Err(e) => return Err(e),
    };

    let mut username = String::new();

    match username_file.read_to_string(&mut username) {
        Ok(_) => Ok(username),
        Err(e) => Err(e),
    }
}

We can shorten the code by using the ? operator. The ? operator can only be used in functions that return a Result type. The ? operator is similar to our unwrap and expect methods. If the Result is Ok, the ? operator will return the value inside the Ok. If the Result is Err, instead of calling the panic! macro, the ? operator will return the error and early exit the function.

If everything is successful, the ? operator will return safely the value inside the Ok.

use std::fs::File;
use std::io::{self, Read};

fn read_username_from_file() -> Result<String, io::Error> {
    let mut username_file = File::open("hello.txt")?;

    let mut username = String::new();

    username_file.read_to_string(&mut username)?;

    Ok(username)
}

We can shorten the code even more by chaining method calls. The ? operator can be used with method calls that return a Result type.

use std::fs::File;
use std::io::{self, Read};

fn read_username_from_file() -> Result<String, io::Error> {
    let mut username = String::new();

    File::open("hello.txt")?.read_to_string(&mut username)?;

    Ok(username)
}

But we can make the code even shorter by using the system module function fs::read_to_string. The fs::read_to_string function will open the file, create a new String, read the contents of the file into the String, and return it. If any of these steps fail, the fs::read_to_string function will return the error.

use std::fs;
use std::io;

fn read_username_from_file() -> Result<String, io::Error> {
    fs::read_to_string("hello.txt")
}

As mentioned before, the ? operator can only be used in functions that return a Result type. If we want to use the ? operator in the main function, we have to change the return type of the main function to Result. The main function can also return a Result type.

use std::error::Error;
use std::fs::File;

fn main() -> Result<(), Box<dyn Error>> {
    let greeting_file = File::open("hello.txt")?;
    Ok(())
}

The main function returns a Result type. The Result type contains a () on success and a Box<dyn Error> on error.

Error helper crates

There are a lot of crates that can help you with error handling. In this section, we will look at the anyhow crate and the thiserror crate. This is not an exhaustive list of error-handling crates, but it will give you an idea of what is out there.

Of course, we can not go to deep into these crates. If you want to learn more about these crates, you can check out the links at the end of this section.

The thiserror crate

thiserror provides a derived implementation which adds the error trait for us. This makes it easier to implement the error trait for our custom error types.

To use the thiserror crate, we have to add the crate to our Cargo.toml file. The cargo add command will add the thiserror crate to our Cargo.toml file.

cargo add thiserror

We can now use the thiserror crate in our code. We will create a custom error type for our read_username_from_file function called CustomError.

use std::error::Error;
use std::fs::File;
use std::io::Read;

#[derive(Debug, thiserror::Error)]
enum CustomError {
    #[error("OMG! There is an error {0}")]
    BadError(#[from] std::io::Error),

}

fn read_username_from_file() -> Result<String, CustomError> {
    let mut username = String::new();
    File::open("hello.txt")?.read_to_string(&mut username)?;
    Ok(username)
}

The anyhow crate

anyhow provides an idiomatic alternative to explicitly handling errors. It is similar to the previously mentioned error trait but has additional features such as adding context to thrown errors.

To add the anyhow crate to our project, we can use the cargo add command.

cargo add anyhow

We can now use the anyhow crate in our code. We will create a custom error type for our read_username_from_file function called CustomError.

use std::fs::File;
use std::io::Read;
use anyhow::Context;


fn read_username_from_file() -> Result<String, anyhow::Error> {
    let mut username = String::new();

    File::open("hello.txt").context("Failed to open file")?.read_to_string(&mut username).context("Failed to read file")?;

    Ok(username)
}

When to use thiserror and anyhow

The thiserror crate is useful when you want to implement the Error trait for your custom error types. The anyhow crate is useful when you don’t care about the error type and just want to add context to the error.

Summary

In this article, we looked at error handling in Rust 🦀. We talked about non-recoverable errors and recoverable errors. The error handling in Rust 🦀 is designed to help you in writing code that is more robust and less error-prone. The panic! macro is used for non-recoverable errors when your program is in a state where it can not continue and should stop instead of trying to proceed with invalid or incorrect data. The Result type is used for recoverable errors. The Result enums indicate that the operation can fail and that our code can recover from the error and the caller of the piece of code has to handle the success or failure of the operation.

Resources

  • Error Handling

  • The anyhow crate

  • The thiserror crate

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)

Elastio Engineering Handbook

Errors

Rust’s try operator (?) and support for sum types (e.g., Result which can be either Ok or Err) make for powerful
and easy-to-reason-about error handling. Unfortunately as of this writing in late 2020 there is no consensus solution
for representing and reporting errors in crates. The existing std::result::Result and std::error::Error types
provide the bare bones for representing successful and failed operations, but they’re not adequate on their own.

In first year or so of Elastio, we used the snafu crate, which is an opinionated
implementation of error handling in Rust that seemed to work well. Many of our crates still use Snafu today, because
there’s no reason to migrate. However recently momentum has been building behind two crates which together are fast
becoming the de facto Right Way(tm) for doing things: anyhow and
thiserror.

anyhow provides a universal Error type which can wrap any std::error::Error implementation and provide some useful
operations. It’s the equivalent of throws Exception in Java. We use anyhow when building CLIs that need to be able
to handle various kinds of errors from different crates and have no need to wrap those errors in a specific error type.
In some cases we also use anyhow when writing tests, when the alternative would be Box<dyn Error> which is nasty.

thiserror is used at Elastio when a library crate needs to expose an error enum as part of it’s API. Where
historically we’ve used snafu, newer crates (and any crates created in the future) use thiserror to build a
CrateNameError variant.

This chapter describes the best practices we’ve evolved with these crates, and should be followed unless there’s a good
reason to do something different.

Official Docs

Most of the details of how to use anyhow and thiserror are covered in their respective docs. None of that will be
repeated here, so before reading the rest of this chapter make sure you’ve reviewed the official docs for both crates
and have a good understanding of how they work in general (and, in particular, how anyhow is different than
thiserror and under what circumstances one should use one versus the other).

Legacy Snafu

As noted above, many of our biggest and most important crates were written before thiserror was clearly the way
forward for library error reporting. Those crates use snafu instead. There’s nothing wrong with snafu, and it’s
similar in design to thiserror in many ways. Those crates that use snafu will continue to do so, and if you find
yourself needing to add or modify the error variants in those crates, you too must use snafu. Porting existing error
handling to thiserror without a compelling reason is not a good use of engineering time.

Having said that, starting a new crate in 2020 or beyond and using snafu to build the error type is also not a good
use of engineering time.

Error variant naming

Each variant of the error enum is obviously an error, because it’s a variant of the error enum. Thus, the word Error
should not appear in the name of the error enum as it’s redundant. So IoError is a bad name for an error; Io is
good.

Error messages

thiserror

When defining an error type with thiserror, it’s easy to define what the error message should be for an error:

#![allow(unused)]
fn main() {
use thiserror::Error;

#[derive(Error, Debug)]
pub enum Error {
    #[error("IO error")]
    Io {
        source: std::io::Error
    }
}
}

You might be tempted to try to make another error type’s message part of your error, like this:

#![allow(unused)]
fn main() {
use thiserror::Error;

#[derive(Error, Debug)]
pub enum Error {
    // WRONG DON'T DO THIS
    #[error("IO error: {source}")]
    Io {
        source: std::io::Error
    }
}
}

You might even find this pattern in our code still. However the latest best practice is to report the error type which
caused a particular error by returning it in the std::error::Error::source() function, which for thiserror means a
field named source or marked with the #[source] or #[from] attributes. Why?

Because it’s often not at all useful to include the error message of an inner error. Maybe that error is itself just a
higher-level error type, and the real cause of the error is nested three or more errors deep. Or maybe you need to know
the details of all of the errors in the chain from root cause on up to your error type in order to understand what
really happened.

Therefore, instead of forcing the nested error’s message into your error message, you should rely on crates like
anyhow (or color-eyre) to pretty-print error types at the point where they are communicated to users (printed to the
console in the case of a CLI, or written to the log in the case of a server).

anyhow

When reporting errors with anyhow, the principle is the same but the mechanics are slightly different. Since
anyhow::Error is a universal container for any error type, you do not use anyhow to define strongly typed errors.
Instead you wrap arbitrary error types in anyhow. But sometimes you have a situation where you have an error e, and
you want to report it as an anyhow::Error but with some additional context to help clarify the error. This is the
wrong way:

#![allow(unused)]

fn main() {
use anyhow::anyhow;

// DO NOT DO THIS
let e = std::io::Error::last_os_error();
// Assume `e` contains an error you want to report
anyhow!(format!("Got an error while trying to frobulate the gonkolator: {}", e));
}

This has the same problem as the thiserror example above. You’re losing all of the information in e other than it’s
message. Maybe it had its own source with valuable context, or a backtrace that would have clarified where this
happened. Instead you should use anyhow to wrap the error in some context:

#![allow(unused)]

fn main() {
use anyhow::anyhow;

// This is the right way
let e = std::io::Error::last_os_error();
// Assume `e` contains an error you want to report
anyhow::Error::new(e).context("Got an error while trying to frobulate the gonkolator");
}

Using anyhow context or with_context

The above example uses context on an anyhow::Error. anyhow also has a Context trait which adds context and
with_context to arbitrary Result types, to make it easier to wrap possible errors in context information.

Be advised that in this case you should avoid allocating strings when calling context. For example:

use anyhow::Context;

fn get_username() -> String {
    // ...
  "foo".to_owned()
}

fn get_host() -> String {
    // ...
  "foo".to_owned()
}

fn main() -> anyhow::Result<()> {

// WRONG
std::fs::File::create("/tmp/f00")
 .context(format!("Error creating file foo for user {} on host {}", get_username(), get_host()))?;

// RIGHT
std::fs::File::create("/tmp/f00")
 .with_context(|| format!("Error creating file foo for user {} on host {}", get_username(), get_host()))?;

Ok(())
}

By passing a closure to with_context, you defer the evaluation of format! unless File::create actually fails. On
success you skip all of this computation and the associated heap allocation, and calls to get_username() and
get_host().

error module

Each library crate should have an error module. This should define a thiserror-based error enum named CrateNameError,
where CrateName is the pascal case representation of the crate’s name.

Many Rust crates and the Rust std lib use an error representation called Error, but this leads to problems with code
clarity when dealing with multiple different crates’ error types.

The error module should also define a type alias called Result, which aliases to std::result::Result with the
default error type set to the crate’s error type, e.g.:

#![allow(unused)]
fn main() {
// In `error.rs`
pub enum CrateNameError { /* ... */ }

pub type Result<T, E = CrateNameError> = std::result::Result<T, E>;
}

If necessary (and only if necessary!) the root of each library crate should expose the error module publically. As of
now the only reason this would be necessary is to expose the Snafu-generated context selectors to other crates, which is
only needed if macros are generating code that needs to use those error types. That’s an edge case; in general error
should be private.

In all cases, the error enum and Result type alias should be re-exported from the root of the crate, e.g.:

// In lib.rs

// The `error` module should not be public except for the edge case described above
mod error;

// but the Error type and the Result type alias should be part of the public API in the root module
pub use error::{Result, CrateNameError};

// And all funcs should use this public re-export

// Note this is using crate::Result, not crate::error::Result
pub fn some_public_func() -> Result<()> {
  todo!()
}

Other modules in the crate should use crate::CrateNameError, NOT use crate::error::CrateNameError. This is for
consistency between crate code and external callers, and also because it’s less typing.

Note that when using Snafu, Snafu’s context selectors should NOT be re-exported this way, and when referenced within other modules in the
crate, those modules should use crate::error, and refer to the context selectors with error::SomeErrorKind.

Using ensure

Anyhow provides the ensure! macro to test a condition and fail with an error if it is false. Prefer this to explicit
if or match expressions unless they add some clarity somehow.

68 stable releases

new
1.0.69
Feb 5, 2023
1.0.68 Dec 18, 2022
1.0.66 Oct 20, 2022
1.0.58 Jun 18, 2022
0.0.0 Oct 5, 2019

#1 in Rust patterns

Download history

969372/week @ 2022-10-21
898252/week @ 2022-10-28
992215/week @ 2022-11-04
955742/week @ 2022-11-11
855829/week @ 2022-11-18
903178/week @ 2022-11-25
910884/week @ 2022-12-02
881247/week @ 2022-12-09
866044/week @ 2022-12-16
533588/week @ 2022-12-23
691677/week @ 2022-12-30
943475/week @ 2023-01-06
913716/week @ 2023-01-13
949346/week @ 2023-01-20
1052273/week @ 2023-01-27
906244/week @ 2023-02-03

3,992,409 downloads per month

Used in 14,288 crates
(7,805 directly)

MIT/Apache


150KB



2.5K

SLoC

Anyhow ¯_(°ペ)_/¯

github
crates.io
docs.rs
build status

This library provides anyhow::Error, a trait object based error type
for easy idiomatic error handling in Rust applications.

[dependencies]
anyhow = "1.0"

Compiler support: requires rustc 1.39+

Details

  • Use Result<T, anyhow::Error>, or equivalently anyhow::Result<T>, as the
    return type of any fallible function.

    Within the function, use ? to easily propagate any error that implements the
    std::error::Error trait.

    use anyhow::Result;
    
    fn get_cluster_info() -> Result<ClusterMap> {
        let config = std::fs::read_to_string("cluster.json")?;
        let map: ClusterMap = serde_json::from_str(&config)?;
        Ok(map)
    }
    
  • Attach context to help the person troubleshooting the error understand where
    things went wrong. A low-level error like «No such file or directory» can be
    annoying to debug without more context about what higher level step the
    application was in the middle of.

    use anyhow::{Context, Result};
    
    fn main() -> Result<()> {
        ...
        it.detach().context("Failed to detach the important thing")?;
    
        let content = std::fs::read(path)
            .with_context(|| format!("Failed to read instrs from {}", path))?;
        ...
    }
    
    Error: Failed to read instrs from ./path/to/instrs.json
    
    Caused by:
        No such file or directory (os error 2)
    
  • Downcasting is supported and can be by value, by shared reference, or by
    mutable reference as needed.

    // 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),
    }
    
  • If using the nightly channel, or stable with features = ["backtrace"], a
    backtrace is captured and printed with the error if the underlying error type
    does not already provide its own. In order to see backtraces, they must be
    enabled through the environment variables described in std::backtrace:

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

    The tracking issue for this feature is rust-lang/rust#53487.

  • Anyhow works with any error type that has an impl of std::error::Error,
    including ones defined in your crate. We do not bundle a derive(Error) macro
    but you can write the impls yourself or use a standalone macro like
    thiserror.

    use thiserror::Error;
    
    #[derive(Error, Debug)]
    pub enum FormatError {
        #[error("Invalid header (expected {expected:?}, got {found:?})")]
        InvalidHeader {
            expected: String,
            found: String,
        },
        #[error("Missing attribute: {0}")]
        MissingAttribute(String),
    }
    
  • One-off error messages can be constructed using the anyhow! macro, which
    supports string interpolation and produces an anyhow::Error.

    return Err(anyhow!("Missing attribute: {}", missing));
    

    A bail! macro is provided as a shorthand for the same early return.

    bail!("Missing attribute: {}", missing);
    

No-std support

In no_std mode, the same API is almost all available and works the same way. To
depend on Anyhow in no_std mode, disable our default enabled «std» feature in
Cargo.toml. A global allocator is required.

[dependencies]
anyhow = { version = "1.0", default-features = false }

Since the ?-based error conversions would normally rely on the
std::error::Error trait which is only available through std, no_std mode will
require an explicit .map_err(Error::msg) when working with a non-Anyhow error
type inside a function that returns Anyhow’s error type.

Comparison to failure

The anyhow::Error type works something like failure::Error, but unlike
failure ours is built around the standard library’s std::error::Error trait
rather than a separate trait failure::Fail. The standard library has adopted
the necessary improvements for this to be possible as part of RFC 2504.

Comparison to thiserror

Use Anyhow if you don’t care what error type your functions return, you just
want it to be easy. This is common in application code. Use thiserror if you
are a library that wants to design your own dedicated error type(s) so that on
failures the caller gets exactly the information that you choose.

License


Licensed under either of Apache License, Version
2.0 or MIT license at your option.



Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in this crate by you, as defined in the Apache-2.0 license, shall
be dual licensed as above, without any additional terms or conditions.

Dependencies


~0–580KB


~12K SLoC

Понравилась статья? Поделить с друзьями:
  • Rust easy anti cheat error
  • Rust eac error
  • Rust dyn error
  • Rust custom error
  • Rust compile error macro