How to manually return a Result<(), Box<dyn Error>>?
Asked Answered
U

3

40

I want to return an error from a function in case a condition is true:

use std::error::Error;

pub fn run() -> Result<(), Box<dyn Error>> {
    // -- snip ---

    if condition {
        // return error
    }

    // -- snip --

    Ok(())
}

fn main() {}

I probably don't have the basics of the typesystem down, but everywhere I looked people use the ? operator, so I can't figure out what type to return.

  1. Is it possible to just return an error like this?
  2. Is there a better way to handle this logic?
Ulda answered 27/7, 2018 at 3:2 Comment(0)
H
46

Error is a trait and you want to return a trait object (note the dyn keyword), so you need to implement this trait:

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

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

impl fmt::Display for MyError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "There is an error: {}", self.0)
    }
}

impl Error for MyError {}

pub fn run() -> Result<(), Box<dyn Error>> {
    let condition = true;

    if condition {
        return Err(Box::new(MyError("Oops".into())));
    }

    Ok(())
}

fn main() {
    if let Err(e) = run() {
        println!("{}", e); // "There is an error: Oops"
    }
}
  • Create your own error type,
  • Implement Debug, Display, then Error for it,
  • If there is an error, return the Err variant of Result.

I advise you to use failure that remove all the error boilerplate:

#[derive(Fail, Debug)]
#[fail(display = "There is an error: {}.", _0)]
struct MyError(String);

--

Note that if you expect an Error, you can return whatever type you want, given that it implements Error. This includes the error types in std.

Horaciohorae answered 27/7, 2018 at 4:45 Comment(5)
Wow, that's a lot... Now I have even more questions... 1. So it's mandatory to create my own error type? Is it not possible to return an error that's already defined in std? 2. How would the code look if I use the compiler attributes that you suggested? 3. what is this dyn??? It's not even listed on Appendix - A of "The Rust Programming Language"!Ulda
@ΣτέφανοςΜανδαλάς I cannot answer right now. In 1 hour or so.Horaciohorae
@ΣτέφανοςΜανδαλάς I updated my answer about your points 1 and 3. The attributes generate the boilerplate code that you are afraid of ;) Furthermore, IIRC, the Fail trait permit more usefull things, but you can read the doc to know more.Horaciohorae
@FrenchBoiethios Can you somehow check for the type MyError instead of just printing the error? I'm trying to use this example to return different error types, but compiler says it's always Box<Error>Habakkuk
You can downcast your error: play.rust-lang.org/…Horaciohorae
C
30

To return custom errors, given that the function is set to return Result<(), Box<dyn Error>>:

fn serve(config: &Config, stream: TcpStream) -> Result<(), Box<dyn Error>> {
    // ...
    if request_is_bad() {
        // This returns immediately a custom "Bad request" error
        Err("Bad request")?;
    }
    // ...
}
Clothespin answered 12/3, 2019 at 15:29 Comment(7)
Err("Bad request")?Durer
return Err("Bad request") was the first thing I tried. Unfortunately the compiler complains: "expected struct std::boxed::Box, found reference"Clothespin
return Err("Bad request".into());Northerly
Works! Also I now see what @Durer meant, because just writing Err("Bad request")? (without return statement and with question mark at the end) also works! I think both of these solutions are much more elegant. Should I edit the answer?Clothespin
Could someone explain why appending ? or using .into() work as a solution? Is one preferred over the other? @Durer @NortherlyHeroic
@Heroic I don't know about other people, but I prefer only using ? when it doesn't always return. Using a literal Err always returns, of course.Valoniah
@Heroic From<&str> is implemented for Box<dyn Error> and both .into() and ? are converting strings to error boxes using that implementation. (see doc.rust-lang.org/std/boxed/…)Uveitis
V
7

Box<dyn Error> is handy for types that implement it:

use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
   Err("March")?
}

but surprisingly, it doesn't work with all types:

use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
   // the trait `std::error::Error` is not implemented for `{integer}`
   Err(9)?
}

as a workaround, you can use what I call the Error Format idiom:

use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
   Err(format!("{}", 9))?
}

Note this has many variations, for example with literals you can do this:

use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
   Err(concat!(9))?
}

and also, you may not even need to use Box<dyn Error>:

fn main() -> Result<(), String> {
   Err(concat!(9))?
}

It can also be useful in cases where you normally don't need it. For example, this example below could work without it, but it's useful as it adds the filename to the error, which normally isn't shown:

use std::fs;
fn main() -> Result<(), String> {
   let s = "a.rs";
   match fs::read_to_string(s) {
      Ok(v) => print!("{}", v),
      // normal message is just: The system cannot find the file specified
      Err(v) => Err(format!("{} {}", s, v))?
   }
   Ok(())
}
Vitriolic answered 1/12, 2020 at 5:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.