I'm having trouble getting miette to give consistent output.
I'd expect the program below to give the same output whether I pass in "good" (which gives the fancy formatting I want) or "bad" (which prints a more debug-like error message), but one way I get things pretty-printed and one I get a much more basic message and I can't figure out why - what's going on?
(This is heavily copied from the this example - note there's on dbg!()
statement at the start, it's the Error: ...
output at the end that I expect to be different.)
use std::error::Error;
use miette::{Diagnostic, SourceSpan};
use miette::{NamedSource, Result as MietteResult};
use thiserror::Error;
#[derive(Error, Debug, Diagnostic)]
#[error("oops!")]
#[diagnostic(
code(oops::my::bad),
url(docsrs),
help("try doing it better next time?")
)]
struct MyBad {
// The Source that we're gonna be printing snippets out of.
// This can be a String if you don't have or care about file names.
#[source_code]
src: NamedSource,
// Snippets and highlights can be included in the diagnostic!
#[label("This bit here")]
bad_bit: SourceSpan,
}
fn this_gives_correct_formatting() -> MietteResult<()> {
let res: Result<(), MyBad> = Err(MyBad {
src: NamedSource::new("bad_file.rs", "source\n text\n here".to_string()),
bad_bit: (9, 4).into(),
});
res?;
Ok(())
}
fn main() -> Result<(), Box<dyn Error>> {
if std::env::args().nth(1).unwrap() == "bad" {
let res: Result<(), MyBad> = Err(MyBad {
src: NamedSource::new("bad_file.rs", "source\n text\n here".to_string()),
bad_bit: (9, 4).into(),
});
dbg!(&res);
res?;
Ok(())
} else if std::env::args().nth(1).unwrap() == "good" {
let res = this_gives_correct_formatting();
dbg!(&res);
res?;
Ok(())
} else {
panic!("Pass either 'good' or 'bad'");
}
}
In my Cargo.toml
:
[dependencies]
miette = { version = "5.5.0", features = ["fancy"] }
thiserror = "1.0.39"
Output from a session:
$ cargo run good
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
Running `target/debug/rust-play good`
[src/main.rs:50] &res = Err(
MyBad {
src: NamedSource {
name: "bad_file.rs",
source: "<redacted>",
,
bad_bit: SourceSpan {
offset: SourceOffset(
9,
),
length: SourceOffset(
4,
),
},
},
)
Error: oops::my::bad (https://docs.rs/rust-play/0.1.0/rust_play/struct.MyBad.html)
× oops!
╭─[bad_file.rs:1:1]
1 │ source
2 │ text
· ──┬─
· ╰── This bit here
3 │ here
╰────
help: try doing it better next time?
$ cargo run bad
Finished dev [unoptimized + debuginfo] target(s) in 0.03s
Running `target/debug/rust-play bad`
[src/main.rs:42] &res = Err(
MyBad {
src: NamedSource {
name: "bad_file.rs",
source: "<redacted>",
,
bad_bit: SourceSpan {
offset: SourceOffset(
9,
),
length: SourceOffset(
4,
),
},
},
)
Error: MyBad { src: NamedSource { name: "bad_file.rs", source: "<redacted>", bad_bit: SourceSpan { offset: SourceOffset(9), length: SourceOffset(4) } }
The "good" case produces an error of type miette::Error
while the "bad" case produces an error of type MyBad
. Presumably the former type has a Display
implementation that produces the fancy human-readable output.
Note that the ?
operator doesn't just return the error in the Err
case, it also attempts to convert it using Into::into()
. x?
is mostly equivalent to:
match x {
Ok(v) => v,
Err(e) => return Err(e.into()),
}
So if x
has type Result<_, E>
, the function is declared with the return type Result<_, F>
, and there is an implementation Into<F>
for E
, the ?
operator will transparently do this conversion. This is easy to miss, so it's understandable that you didn't catch it.
If you replace the return type of main()
with MietteResult<()>
, you should get a compile-time error that the return types of the two conditional blocks don't match.
You can fix this by converting the error value in the "bad" case to miette::Error
:
let res: Result<(), miette::Error> = Err(MyBad { ... }.into());