Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Returning error message to function expecting 'Box<dyn Error>'

I am new to Rust and am trying to propagate errors to be handled in the calling function. From the official rust book I read that Result 'Box< dyn Error>' is used to say capture any type of error but I haven't read far enough to understand how it actually works.

I have a function called:

fn foo() -> Result<String, Box<dyn Error>> {
    Command::new("an_executable")
        .args(&["-p", path])
        .output()?;
    if condition {
        return Err("Error...");
    }
    // Do stuff, return String 
}

So can someone explain how I should be returning an error if a condition is met with that return type. Do i have to change the return type or just return something different. what would the RUST standard be in this scenario?

The current compilation error is that Err("Error...") mismatched with the return type

like image 440
theBigCheese88 Avatar asked Oct 15 '19 11:10

theBigCheese88


1 Answers

Let's focus on the absolute minimum reproduction of your issue:

use std::error::Error;

fn foo() -> Result<String, Box<dyn Error>> {
    Err("Error...")
}

The error returned by this code is:

error[E0308]: mismatched types
 --> src/lib.rs:4:9
  |
4 |     Err("Error...")
  |         ^^^^^^^^^^ expected struct `std::boxed::Box`, found reference
  |
  = note: expected type `std::boxed::Box<dyn std::error::Error>`
             found type `&'static str`

It's saying that the function signature expected you to return an Err containing a Box<dyn Error>, but you actually returned an Err containing a &str. Since the types don't line up, the compiler throws an error.

The easiest way to fix this is to use the Into trait, which implements a conversion between &str and Box<dyn Error>:

use std::error::Error;

fn foo() -> Result<String, Box<dyn Error>> {
    Err("Error...".into())
    // `Err(Box::from("Error..."))` would also work
}

How it works

You may still be wondering what exactly that magic .into() call is doing behind the scenes.

First, let's see what happens if we just Box the &str:

use std::error::Error;

fn foo() -> Result<String, Box<dyn Error>> {
    Err(Box::new("Error..."))
}
error[E0277]: the trait bound `&str: std::error::Error` is not satisfied
 --> src/lib.rs:4:9
  |
4 |     Err(Box::new("Error..."))
  |         ^^^^^^^^^^^^^^^^^^^^ the trait `std::error::Error` is not implemented for `&str`
  |
  = note: required for the cast to the object type `dyn std::error::Error`

Again, this doesn't work because the types don't line up - it's expecting the Box to contain something that implements the Error trait, but if you look at the docs, you'll notice that &str is not one of the types that implements it. You need to wrap your string in a type that does implement Error:

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

#[derive(Debug)]
struct StrError<'a>(&'a str);

// Error doesn't require you to implement any methods, but
// your type must also implement Debug and Display.
impl<'a> Error for StrError<'a> {}

impl<'a> fmt::Display for StrError<'a>{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        // Delegate to the Display impl for `&str`:
        self.0.fmt(f)
    }
}

fn foo() -> Result<String, Box<dyn Error>> {
    Err(Box::new(StrError("Error...")))
}

This code compiles, and is basically exactly what impl Into<Box<dyn Error>> for &str does under the hood :)

like image 137
Joe Clay Avatar answered Oct 21 '22 04:10

Joe Clay