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?
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 impl
s 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!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With