How to use the Error trait with backtraces

Enable backtraces by setting the `RUST_BACKTRACE` environment variable and ensuring your error types implement the `std::error::Error` trait, which allows the standard library to capture stack traces automatically when using `std::backtrace::Backtrace`.

Enable backtraces by setting the RUST_BACKTRACE environment variable and ensuring your error types implement the std::error::Error trait, which allows the standard library to capture stack traces automatically when using std::backtrace::Backtrace. For production code, you typically combine this with the backtrace crate or the anyhow/thiserror ecosystem to attach backtraces directly to error structs for better context.

Here is a minimal example using the standard library and the backtrace crate to manually capture and print a trace:

use std::error::Error;
use std::fmt;
use backtrace::Backtrace;

#[derive(Debug)]
struct MyError {
    msg: String,
    backtrace: Backtrace,
}

impl fmt::Display for MyError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Error: {}", self.msg)
    }
}

impl Error for MyError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        None
    }
}

fn trigger_error() -> Result<(), MyError> {
    Err(MyError {
        msg: "Something went wrong".to_string(),
        backtrace: Backtrace::new(),
    })
}

fn main() {
    if let Err(e) = trigger_error() {
        eprintln!("{}", e);
        eprintln!("Backtrace:\n{}", e.backtrace);
    }
}

For a more ergonomic approach that automatically captures backtraces without manual struct wrapping, use the anyhow crate. It handles the RUST_BACKTRACE logic internally and attaches the trace to the error when you call .context() or .with_context().

use anyhow::{Context, Result};

fn do_work() -> Result<()> {
    let x: Option<i32> = None;
    x.ok_or_else(|| anyhow::anyhow!("Value is missing"))
        .context("Failed to retrieve value")?;
    Ok(())
}

fn main() {
    if let Err(e) = do_work() {
        // Prints the error message and the full backtrace
        eprintln!("{:?}", e); 
    }
}

To see the backtrace in the standard library example, run your binary with RUST_BACKTRACE=1 cargo run. For anyhow, the backtrace is included in the debug output by default if the environment variable is set, or you can force it via eprintln!("{:?}", e) which uses the Debug implementation that includes the trace. Remember that backtraces add a small runtime cost and increase binary size, so they are often disabled in optimized production builds unless explicitly requested via environment variables or feature flags.