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.