├── .gitignore ├── Cargo.toml ├── README.md └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | .vscode -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "backtrace-error" 3 | version = "0.5.1" 4 | edition = "2021" 5 | rust-version = "1.65" 6 | description = "wrap errors with automatic backtrace capture and print-on-result-unwrap" 7 | authors = ["Graydon Hoare "] 8 | license = "MIT OR Apache-2.0" 9 | keywords = ["error", "backtrace"] 10 | repository = "http://github.com/graydon/backtrace-error" 11 | readme = "README.md" 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # backtrace-error 2 | 3 | 4 | This is a tiny crate that provides a tiny error-wrapper struct 5 | `BacktraceError` with only two features: 6 | 7 | - Captures a backtrace on `From`-conversion from its wrapped type (if 8 | `RUST_BACKTRACE` is on etc.) 9 | - Pretty-prints that backtrace in its `Display` implementation. 10 | 11 | It also includes an extension trait `ResultExt` that you can `use` to give 12 | you `.unwrap_or_backtrace` and `.expect_or_backtrace` methods on any 13 | `Result>`. These methods do do the same as `unwrap` 14 | or `expect` on `Result` except they pretty-print the backtrace on `Err`, 15 | before panicking. 16 | 17 | Finally, it provides a _dynamic_ variant in case you want to type-erase the 18 | error type, `DynBacktraceError`. This works the same as `BacktraceError` 19 | but wraps a `Box` instead of requiring a 20 | specific error type `E`, so is therefore potentially more expensive but also 21 | more flexible and usable as an "any error" catchall type since it has an 22 | `impl From` conversion. 23 | 24 | ## Example 25 | 26 | Usage is straightforward: put some existing error type in it. No macros! 27 | 28 | ```rust 29 | use backtrace_error::{BacktraceError,ResultExt}; 30 | use std::{io,fs}; 31 | 32 | type IOError = BacktraceError; 33 | 34 | fn open_file() -> Result { 35 | Ok(fs::File::open("/does-not-exist.nope")?) 36 | } 37 | 38 | fn do_stuff() -> Result 39 | { 40 | open_file() 41 | } 42 | 43 | fn main() 44 | { 45 | // This will panic but first print a backtrace of 46 | // the error site, then a backtrace of the panic site. 47 | let file = do_stuff().unwrap_or_backtrace(); 48 | } 49 | ``` 50 | 51 | or dynamically: 52 | 53 | ```rust 54 | use backtrace_error::{DynBacktraceError,ResultExt}; 55 | use std::{io,fs}; 56 | 57 | type AppErr = DynBacktraceError; 58 | 59 | fn open_file() -> Result { 60 | Ok(fs::File::open("/does-not-exist.nope")?) 61 | } 62 | 63 | fn parse_number() -> Result { 64 | Ok(i32::from_str_radix("not-a-number", 10)?) 65 | } 66 | 67 | fn do_stuff() -> Result<(), AppErr> 68 | { 69 | open_file()?; 70 | parse_number()?; 71 | Ok(()) 72 | } 73 | 74 | fn main() 75 | { 76 | // This will panic but first print a backtrace of 77 | // the error site, then a backtrace of the panic site. 78 | do_stuff().unwrap_or_backtrace(); 79 | } 80 | ``` 81 | 82 | I am very sorry for having written Yet Another Rust Error Crate but 83 | strangely everything I looked at either doesn't capture backtraces, doesn't 84 | print them, only debug-prints them on a failed unwrap (which is illegible), 85 | provides a pile of features I don't want through expensive macros, or some 86 | combination thereof. I don't need any of that, I just want to capture 87 | backtraces for errors when they occur, and print them out sometime later. 88 | 89 | I figured maybe someone out there has the same need, so am publishing it. 90 | 91 | License: MIT OR Apache-2.0 92 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021-2024 Graydon Hoare 2 | // Licensed under ASL2 or MIT 3 | 4 | //! 5 | //! This is a tiny crate that provides a tiny error-wrapper struct 6 | //! `BacktraceError` with only two features: 7 | //! 8 | //! - Captures a backtrace on `From`-conversion from its wrapped type (if 9 | //! `RUST_BACKTRACE` is on etc.) 10 | //! - Pretty-prints that backtrace in its `Display` implementation. 11 | //! 12 | //! It also includes an extension trait `ResultExt` that you can `use` to give 13 | //! you `.unwrap_or_backtrace` and `.expect_or_backtrace` methods on any 14 | //! `Result>`. These methods do do the same as `unwrap` 15 | //! or `expect` on `Result` except they pretty-print the backtrace on `Err`, 16 | //! before panicking. 17 | //! 18 | //! Finally, it provides a _dynamic_ variant in case you want to type-erase the 19 | //! error type, `DynBacktraceError`. This works the same as `BacktraceError` 20 | //! but wraps a `Box` instead of requiring a 21 | //! specific error type `E`, so is therefore potentially more expensive but also 22 | //! more flexible and usable as an "any error" catchall type since it has an 23 | //! `impl From` conversion. 24 | //! 25 | //! # Example 26 | //! 27 | //! Usage is straightforward: put some existing error type in it. No macros! 28 | //! 29 | //! ```should_panic 30 | //! use backtrace_error::{BacktraceError,ResultExt}; 31 | //! use std::{io,fs}; 32 | //! 33 | //! type IOError = BacktraceError; 34 | //! 35 | //! fn open_file() -> Result { 36 | //! Ok(fs::File::open("/does-not-exist.nope")?) 37 | //! } 38 | //! 39 | //! fn do_stuff() -> Result 40 | //! { 41 | //! open_file() 42 | //! } 43 | //! 44 | //! fn main() 45 | //! { 46 | //! // This will panic but first print a backtrace of 47 | //! // the error site, then a backtrace of the panic site. 48 | //! let file = do_stuff().unwrap_or_backtrace(); 49 | //! } 50 | //! ``` 51 | //! 52 | //! or dynamically: 53 | //! 54 | //! ```should_panic 55 | //! use backtrace_error::{DynBacktraceError,ResultExt}; 56 | //! use std::{io,fs}; 57 | //! 58 | //! type AppErr = DynBacktraceError; 59 | //! 60 | //! fn open_file() -> Result { 61 | //! Ok(fs::File::open("/does-not-exist.nope")?) 62 | //! } 63 | //! 64 | //! fn parse_number() -> Result { 65 | //! Ok(i32::from_str_radix("not-a-number", 10)?) 66 | //! } 67 | //! 68 | //! fn do_stuff() -> Result<(), AppErr> 69 | //! { 70 | //! open_file()?; 71 | //! parse_number()?; 72 | //! Ok(()) 73 | //! } 74 | //! 75 | //! fn main() 76 | //! { 77 | //! // This will panic but first print a backtrace of 78 | //! // the error site, then a backtrace of the panic site. 79 | //! do_stuff().unwrap_or_backtrace(); 80 | //! } 81 | //! ``` 82 | //! 83 | //! I am very sorry for having written Yet Another Rust Error Crate but 84 | //! strangely everything I looked at either doesn't capture backtraces, doesn't 85 | //! print them, only debug-prints them on a failed unwrap (which is illegible), 86 | //! provides a pile of features I don't want through expensive macros, or some 87 | //! combination thereof. I don't need any of that, I just want to capture 88 | //! backtraces for errors when they occur, and print them out sometime later. 89 | //! 90 | //! I figured maybe someone out there has the same need, so am publishing it. 91 | 92 | use std::{ 93 | backtrace::Backtrace, 94 | error::Error, 95 | fmt::{Debug, Display}, 96 | ops::{Deref, DerefMut}, 97 | }; 98 | 99 | pub struct BacktraceError { 100 | pub inner: E, 101 | pub backtrace: Box, 102 | } 103 | 104 | impl Display for BacktraceError { 105 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 106 | writeln!(f, "Initial error: {:}", self.inner)?; 107 | writeln!(f, "Error context:")?; 108 | writeln!(f, "{:}", self.backtrace) 109 | } 110 | } 111 | 112 | impl Debug for BacktraceError { 113 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 114 | ::fmt(self, f) 115 | } 116 | } 117 | 118 | impl Error for BacktraceError { 119 | fn source(&self) -> Option<&(dyn Error + 'static)> { 120 | Some(&self.inner) 121 | } 122 | } 123 | 124 | // Someday we'll also support the "Provider" API, but not today 125 | // since it is not stable and I don't want to bother tracking 126 | // its stability. 127 | /* 128 | impl std::any::Provider for BacktraceError { 129 | fn provide<'a>(&'a self, demand: &mut std::any::Demand<'a>) { 130 | demand.provide_ref::(self.backtrace) 131 | .provide_value::(|| self.backtrace) 132 | } 133 | } 134 | */ 135 | 136 | impl From for BacktraceError { 137 | fn from(inner: E) -> Self { 138 | let backtrace = Box::new(Backtrace::capture()); 139 | Self { inner, backtrace } 140 | } 141 | } 142 | 143 | pub trait ResultExt: Sized { 144 | type T; 145 | fn unwrap_or_backtrace(self) -> Self::T { 146 | self.expect_or_backtrace("ResultExt::unwrap_or_backtrace found Err") 147 | } 148 | fn expect_or_backtrace(self, msg: &str) -> Self::T; 149 | } 150 | 151 | impl ResultExt for Result> { 152 | type T = T; 153 | fn expect_or_backtrace(self, msg: &str) -> T { 154 | match self { 155 | Ok(ok) => ok, 156 | Err(bterr) => { 157 | eprintln!("{}", msg); 158 | eprintln!(""); 159 | eprintln!("{:}", bterr); 160 | panic!("{}", msg); 161 | } 162 | } 163 | } 164 | } 165 | 166 | pub struct DynBacktraceError { 167 | inner: Box, 168 | backtrace: Box, 169 | } 170 | 171 | impl From for DynBacktraceError { 172 | fn from(inner: E) -> Self { 173 | let backtrace = Box::new(Backtrace::capture()); 174 | Self { 175 | inner: Box::new(inner), 176 | backtrace, 177 | } 178 | } 179 | } 180 | 181 | impl Deref for DynBacktraceError { 182 | type Target = dyn Error + Send + Sync + 'static; 183 | fn deref(&self) -> &Self::Target { 184 | &*self.inner 185 | } 186 | } 187 | 188 | impl DerefMut for DynBacktraceError { 189 | fn deref_mut(&mut self) -> &mut Self::Target { 190 | &mut *self.inner 191 | } 192 | } 193 | 194 | impl Display for DynBacktraceError { 195 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 196 | writeln!(f, "Initial error: {:}", self.inner)?; 197 | writeln!(f, "Error context:")?; 198 | writeln!(f, "{:}", self.backtrace) 199 | } 200 | } 201 | 202 | impl Debug for DynBacktraceError { 203 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 204 | ::fmt(self, f) 205 | } 206 | } 207 | 208 | impl ResultExt for Result<(), DynBacktraceError> { 209 | type T = (); 210 | fn expect_or_backtrace(self, msg: &str) -> () { 211 | match self { 212 | Ok(()) => (), 213 | Err(bterr) => { 214 | eprintln!("{}", msg); 215 | eprintln!(""); 216 | eprintln!("{:}", bterr); 217 | panic!("{}", msg); 218 | } 219 | } 220 | } 221 | } 222 | --------------------------------------------------------------------------------