# miette: pretty Rust diagnostics for people who are not compiler hackers

> miette is a diagnostic library and protocol for Rust that extends std::error::Error with error codes, labelled source snippets and a fancy terminal printer. It is aimed at application authors who want readable failures without writing a rendering layer.

**zkat/miette** — Fancy extension for std::error::Error with pretty, detailed diagnostic printing.

- Repository: https://github.com/zkat/miette
- Website: https://docs.rs/miette
- Stars: 2,609 · Forks: 166
- Language: Rust
- License: Apache-2.0
- Published: 2026-09-28 · Updated: 2026-09-28 · Language: en
- Canonical page: https://hysenlabs.com/projects/zkat-miette

## What miette solves, and who should care

A Rust program that returns Box<dyn Error> prints one line. The chain of causes, the file and column where parsing stopped, and the fix you already know about all get lost. miette exists to carry that information through the type system and print it. The crate describes itself as a diagnostic library and a protocol: a set of traits you implement so that your error types can describe themselves, plus a reporter that renders the description.

The intended audience is application authors. The README is explicit that libraries should return concrete error types and that the fancy printer belongs in the top-level crate. If you are writing a library that other people call, you can still derive Diagnostic, because a miette error is a std::error::Error and consumers who have never heard of miette can treat it as one. What you should not do is turn on fancy in a library, since the README says the feature pulls in dependencies that libraries might not want.

The people who get the most out of it are the ones whose errors point at something. A parser, a config loader, a linter, a code generator. If your failures are of the form "connection refused" and nothing else, miette is more machinery than the problem needs.

## The Diagnostic protocol and the derive macro

The core abstraction is the Diagnostic trait. It is generic, and the README states it is compatible with and dependent on std::error::Error. Anything that implements the standard error trait can implement Diagnostic, either by hand or through the derive macro shipped in the miette-derive crate, which is an optional dependency enabled by the default derive feature.

The attributes the README shows are code, url, help, label and source_code. In the worked example, a struct derives Error, Debug and Diagnostic, declares code(oops::my::bad) and url(docsrs), attaches a help string, and marks two fields: a NamedSource<String> tagged #[source_code] and a SourceSpan tagged #[label("This bit here")]. The span is constructed from a tuple, (9, 4).into(), which the example uses to point at an offset in the named source.

That split between source text and span is the mechanism worth understanding. The diagnostic does not hold the highlighted text. It holds the whole source and an offset into it, and the printer decides how much context to show. This is why the same error can render as a single-line highlight or as a multi-line one, and why the README lists single- and multi-line highlighting as separate features of the bundled report handler.

## Installing miette and getting a first labelled error

The README gives two install commands. The first adds the crate with its default features; the second adds it with the fancy printer, which is what produces the screenshots in the repository.

```bash
cargo add miette
cargo add miette --features fancy
```

After that, the smallest useful program defines an error type with thiserror and miette's derive macro. The README's example uses a struct with a named source and a span, and a function returning miette::Result<()>.

```rust
use miette::{Diagnostic, NamedSource, SourceSpan};
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 {
    #[source_code]
    src: NamedSource<String>,
    #[label("This bit here")]
    bad_bit: SourceSpan,
}
```

The function that builds the error takes a plain string, wraps it with NamedSource::new("bad_file.rs", src) and sets bad_bit to (9, 4).into(). Returning that error from a function whose signature is miette::Result<()> is enough to get the report printed, because the README says returning Result<()> is all that is needed and that the default reporter can be swapped with miette::set_hook(). What the reader should see, according to the example, is the error line, a snippet of bad_file.rs with the labelled region highlighted, and the help text underneath.

## The fancy printer, screen readers and colour detection

The bundled ReportHandler renders ANSI and Unicode output. It is not unconditional. The README says a screen-reader-oriented printer is enabled in various situations, including when NO_COLOR or CLICOLOR settings are present, or on CI, and that the behaviour is configurable. The narrated output in the README replaces the box drawing with sentences: a line beginning the snippet, a line showing the source, a line stating where the highlight starts and what its label says, then the help text and the error code.

This is a deliberate trade-off rather than a fallback. A graphical diagnostic that only works when a human is looking at a colour terminal excludes anyone piping output to a file, running in a CI log, or using a screen reader. miette's answer is a second rendering of the same underlying data, which is possible only because the diagnostic carries structure instead of a preformatted string. If you have ever built an error message by concatenating text, this is the part that is hard to retrofit.

The fancy feature is also where the dependency weight lives. The Cargo.toml lists owo-colors, textwrap, supports-hyperlinks, supports-color, supports-unicode, terminal_size, backtrace, backtrace-ext and syntect behind optional features, with fancy-base pulling in the first two. The README's warning about keeping fancy at the top level follows directly from that list.

## Where miette is the wrong choice

The README states plainly that libraries should always return concrete types and that the fancy feature belongs in the top-level crate. If you ignore that and enable fancy in a library, every downstream binary inherits the colour, terminal-size and syntax-highlighting dependencies whether it wants them or not. That is a real cost, and it is not something the crate can undo for you.

There is a second boundary. miette is a reporting layer, not a recovery layer. Nothing in the protocol helps you retry, fall back or classify an error programmatically. The error code attribute gives you a stable identifier, but the README does not describe a matching or dispatch API built on it, so if your program needs to branch on failure kind, you still need your own enum and your own match.

The third case is scale. If your application prints thousands of diagnostics, the snippet rendering, terminal width detection and optional syntax highlighting are work you are paying for on each report. The README does not document a batching or streaming mode; it presents the printer as something that runs when a Result is returned. For a compiler-style tool emitting many diagnostics, that is a design constraint to check before adopting.

## miette against anyhow and eyre

The README positions miette as a replacement for the anyhow and eyre types, offering Result, Report and the miette! macro in place of anyhow! and eyre!. The difference is what the error carries. anyhow and eyre are built around type-erased errors with context strings attached as the error propagates; the resulting report is a chain of messages. miette keeps a structured Diagnostic at the centre, so the report can include a code, a URL, a help line, a severity, related errors, labels and a span into a named source.

That structure has a cost in ceremony. With anyhow you write .context("failed to read config") and move on. With miette you define a type, derive Diagnostic, and decide which fields are sources and which are labels. For a small tool, that is more code than the problem justifies. For a tool whose errors point at a line in a file the user wrote, the extra type is what makes the output useful.

The two are not mutually exclusive in practice. The README's library guidance is to define concrete error types and let consumers treat them as std::error::Error, which is the same discipline anyhow users apply when they keep library errors concrete and convert at the application boundary. The choice is really about what the application boundary should carry: a message, or a diagnostic.

## Maintenance, licence and upgrade cost

The repository is not archived, and the last push was on 2026-06-25. The most recent release listed is v7.1.0 from 2024-02-16, while the Cargo.toml in the repository declares version 7.6.0. That gap between the release list and the manifest is worth noting: if you depend on a specific minor version, confirm it is actually published rather than assuming the manifest reflects what is on the registry.

The crate is Apache-2.0, matching the LICENSE file at the repository root. Apache-2.0 is permissive and includes an explicit patent grant, which matters for companies that have opinions about that clause. This is a description of the licence text, not legal advice; if the patent grant or the notice requirements affect you, that is a question for your own counsel.

Upgrade cost is mostly about the derive macro and the feature set. The manifest sets rust-version to 1.82.0 and edition to 2018, so old toolchains are a hard blocker. The Cargo.toml also lists a no-format-args-capture feature, which suggests the crate has had to accommodate code written before inline format arguments were available. Beyond that, the README does not document a migration guide or a deprecation policy, so a major version bump should be treated as something to read the changelog for rather than assume is mechanical.

## Conclusion

Adopt miette in binaries and tools where a human reads the failure: CLI applications, build tools, anything that points at a line in a source file. Libraries should use the derive macro but leave the fancy feature to the top-level crate, since the README warns that fancy pulls in dependencies libraries may not want. Before committing, check the Cargo.toml rust-version of 1.82.0 against your toolchain, and decide whether you need the derive feature at all, because that is the only default feature.

## FAQ

### How do I add miette to a Rust project?

The README gives cargo add miette for the default features, which include the derive macro, and cargo add miette --features fancy for the graphical printer shown in the screenshots. The README advises enabling fancy only in the top-level crate.

### Does miette work with thiserror?

Yes. The README's examples derive both thiserror::Error and miette::Diagnostic on the same type, and it calls thiserror a great way to define error types that plays nicely with miette. The derive macro is not required; the Diagnostic trait can be implemented directly.

### Can a library depend on miette without pulling in the fancy printer?

The README says miette is fully compatible with library usage and that consumers who do not know about miette can use its error types as regular std::error::Error. It also says the fancy feature should only be enabled in the top-level crate because it pulls in dependencies libraries might not want.

### What does the Diagnostic derive macro accept?

The README shows code, url, help, label and source_code attributes, plus a transparent form for wrapping another Diagnostic. The label attribute goes on a SourceSpan field and source_code on a field holding the text being pointed at, such as a NamedSource<String>.

## Sources

- [License: Apache-2.0](https://github.com/zkat/miette/blob/main/LICENSE)
- [Project website](https://docs.rs/miette)
- [README](https://github.com/zkat/miette/blob/main/README.md)
- [Releases](https://github.com/zkat/miette/releases)
- [zkat/miette on GitHub](https://github.com/zkat/miette)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/zkat-miette
