baNaNa
baNaNa

Reputation: 802

Is there a Rust Result method like expect but which propagates the error instead of panicking?

You can unwrap a Result (panic on Err) with a custom message using expect. How can you propagate an error with a custom message (rather than the default message with ?)?

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

fn main() -> Result<()> {
    let a = Err(Error::new(ErrorKind::other, "default message"));
    // Let's pretend a is a result automatically created by some other method.
    
    let b = a.unwrap();                  // panics with default message
    let b = a.expect("custom message");  // panics with custom message
    
    let b = a?;                          // Returns Err with default message
    let b = /* What goes here? */        // Returns Err with custom message
}

Upvotes: 1

Views: 1628

Answers (1)

cdhowie
cdhowie

Reputation: 168988

There's a few ways to handle this. First, recognize that there is no standard "custom error message" in this context, because in Result<T, E>, the E type can be any type at all, even one that doesn't implement std::error::Error. So, the analogy to the panicking methods doesn't exactly hold. In particular, note that a? does not return "Err with default message!" (See below for an explanation.) What you might be looking for instead is a mechanism to convert from one error type to another.

One way to do this is with Result::map_err, which maps one error value to another.

struct ErrorA;
struct ErrorB;

fn foo() -> Result<(), ErrorA> { todo!() }

fn example() -> Result<(), ErrorB> {
    let a = foo();
    
    let b = a.map_err(|_| ErrorB);
    
    b
}

However, you can make this automatic by realizing that ? operator performs Into-based conversion of the error. The expression a? is roughly equivalent to:

match a {
    Ok(v) => v,
    Err(e) => return Err(std::convert::Into::into(e)),
}

This means it will automatically convert between error types if a suitable Into impl exists. For example:

struct ErrorA;
struct ErrorB;

impl From<ErrorA> for ErrorB {
    fn from(_: ErrorA) -> Self { Self }
}

fn foo() -> Result<(), ErrorA> { todo!() }

fn example() -> Result<(), ErrorB> {
    let a = foo();
    
    let b = a?; // Conversion to ErrorB is implicit
    
    Ok(b)
}

If you really want just a "custom string" error then you can use String or &'static str as the error type. This can be problematic in some contexts though, because neither of these types implement std::error::Error. However, there is a conversion from both to Box<dyn Error>, so if you don't want to define your own error type you can do something like:

struct ErrorA;

fn foo() -> Result<(), ErrorA> { todo!() }

fn example() -> Result<(), Box<dyn std::error::Error>> {
    let a = foo();
    
    // ? converts the &str to Box<dyn Error>
    let b = a.map_err(|_| "custom error message")?;
    
    Ok(b)
}

This approach is suitable for simple Rust programs, but generally shouldn't be used in Rust libraries. Custom error types are more suitable for libraries as they allow callers to distinguish different failure scenarios.


Further reading:

Upvotes: 3

Related Questions