Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is String a valid Error type when it can be reported immediately in stdout? [closed]

I recently implemented basic mechanics of a game of chess and used the Result<T, E> type for methods collecting human input, since it may be invalid. However, I'm not sure about what type I should pick for the possible error (E).

I have gathered that introducing new types is considered a good practice when building a library. However, when the Result can be handled immediately and the Err reported in stdout, isn't it simpler to just return Result<T, String>s or Result<T, &str>s (or Result<T, Cow<str>>s if both can occur)?

Consider the following case:

pub fn play() {
    let mut game = Game::new();

    loop {
        match game.turn() {
            Ok(()) => { game.turn += 1 }
            Err(e) => println!("{}", e)
        }
    }
}

The game is played in the terminal and any input errors can immediately be reported. Is there any added value to introducing a custom error type in this case?

like image 639
ljedrz Avatar asked Aug 28 '16 18:08

ljedrz


1 Answers

This is a rather broad question and there is no clear "right" or "wrong" answer.

It's important to note in your example, that strings carry very little easily accessible semantic information. Sure, you might be able to extract all semantic information by parsing the string, but this is really the wrong approach. Therefore, most bigger libraries or applications use error types that carry more semantic information to allow for easy error handling.

In your case, strings are probably fine, if you will print them immediately anyway. But there is a neat little trick in order to make at least the function signatures a bit more future proof: return Box<Error>.

The Error trait is a nice abstraction over errors. Pretty much every error type implements this trait. With the ? operator and the Into trait, it's possible to handle most errors with ease. Furthermore: there are a few type conversion impls for strings and Box<Error>. This allows to return strings as errors:

use std::error::Error;

fn foo() -> Result<(), Box<dyn Error>> {
    std::fs::File::open("not-here")?; // io::Error
    Err("oh noooo!")?;   // &str
    Err("I broke it :<".to_owned())?;  // String
    Err("nop".into())
}

fn main() {
    println!("{}", foo().unwrap_err());
}

See the working demo.

Edit: please note, that Box<Error> carries less semantic information than another concrete error type like io::Error. So it's not a good idea to always return Box<Error>! It's just a better approach in your situation :)

Edit 2: I've read a lot on error handling models recently, which changed my opinion a bit. I still think this answer is pretty much true. However, I think it's by far not as easy as I formulated it here. So just keep in mind that this answer doesn't suit as general guide at all!

like image 74
Lukas Kalbertodt Avatar answered Dec 15 '22 05:12

Lukas Kalbertodt