A question I haven't been able to wrap my head around:
Why does [std::error::Error] require [std::fmt::Display], but then seems to use [std::fmt::Debug] instead?
For example, running this:
/// Empty struct, used as an Error-type
struct MyError;
impl std::fmt::Debug for MyError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "the `Debug` message")
}
}
impl std::fmt::Display for MyError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "the `Display` message")
}
}
impl std::error::Error for MyError { }
fn main() -> Result<(), MyError> {
Err(MyError)
}
The main function is bound not by Error, but by Termination (note the blanket implementations). As you can see, it's based around Debug.
I have seen some remorse that it was not based around Display, and I agree -- user-friendly messages still require wrapping main, a pattern that Termination aimed to obsolete (but failed IMO).
Error types themselves don't "use" either Display or Debug. The Display bound on errors is an additional guarantee towards the eventual consumer of errors, which says "all errors must be displayable", and therefore whoever actually ends up getting an error can print it.
This has nothing to do with Result-in-main; there are a lot more places where you can handle and print an error. It happens to be the case that main doesn't use Display. But whether a trait is used internally is not necessarily the point of adding a trait bound. In this case, it's much more about adding a guarantee to all types often used in public APIs.
Result returned from main is meant for short examples and quick prototyping, and not for quality programs shipped to users. If you want errors to print a nice message, use:
fn main() {
if let Err(e) = run() {
eprintln!("Nice error info here: {}", e);
std::process::exit(1);
}
}
If you use anyhow in your main application, then you’ll get the Display message, too. (They achieve this by using the Error’s Display implementation for any encapsulated dyn Error, even for the Debug implementation of anyhow::Error; this is done precisely for supporting the use-case of using it as return type from main.)
/// Empty struct, used as an Error-type
struct MyError;
impl std::fmt::Debug for MyError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "the `Debug` message")
}
}
impl std::fmt::Display for MyError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "the `Display` message")
}
}
impl std::error::Error for MyError { }
fn main() -> anyhow::Result<()> {
Err(MyError)?
}
compiling playground v0.0.1 (/playground)
Finished dev [unoptimized + debuginfo] target(s) in 1.16s
Running `target/debug/playground`
Error: the `Display` message
I take it the #[test] macro also uses the same Termination trait-bound then. Replacing main with a #[test] like so:
/// Empty struct, used as an Error-type
struct MyError;
impl std::fmt::Debug for MyError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "the `Debug` message")
}
}
impl std::fmt::Display for MyError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "the `Display` message")
}
}
impl std::error::Error for MyError { }
#[test]
fn test_me() -> Result<(), MyError> {
Err(MyError)
}
Produces:
running 1 test
test test_me ... FAILED
failures:
---- test_me stdout ----
Error: the `Debug` message // <= (Author-added: here)
thread 'test_me' panicked at 'assertion failed: `(left == right)`
left: `1`,
right: `0`: the test returned a termination value with a non-zero status code (1) which indicates a failure', /rustc/c8dfcfe046a7680554bf4eb612bad840e7631c4b/library/test/src/lib.rs:194:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
test_me
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s