From f39eaff0b2c8306dc86ca54e62bc12f6ff1e604e Mon Sep 17 00:00:00 2001 From: Jane Lusby Date: Sun, 5 Jul 2020 19:04:31 -0700 Subject: [PATCH] Switch report handler to a global hook (#29) * Switch report handler to a global hook * more cleanup * remove default from example * remove broken support for no_std * back out the unneeded nostd check * remove one stackframe when capturing a Handler * update docs and prep for release * port lib.rs docs updates to readme --- Cargo.toml | 7 +- README.md | 67 ++---- build.rs | 3 - examples/custom_handler.rs | 81 +++++++ src/chain.rs | 12 - src/context.rs | 57 ++--- src/error.rs | 293 ++++++++++-------------- src/fmt.rs | 6 +- src/kind.rs | 68 +----- src/lib.rs | 451 ++++++++++++++++--------------------- src/wrapper.rs | 4 - tests/test_downcast.rs | 4 +- tests/test_repr.rs | 3 +- tests/test_source.rs | 2 +- tests/ui/no-impl.stderr | 12 +- 15 files changed, 458 insertions(+), 612 deletions(-) create mode 100644 examples/custom_handler.rs diff --git a/Cargo.toml b/Cargo.toml index 23ae22b..f9cd443 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "eyre" -version = "0.5.0" # remember to update html_root_url +version = "0.6.0" # remember to update html_root_url authors = ["David Tolnay ", "Jane Lusby "] edition = "2018" license = "MIT OR Apache-2.0" @@ -10,10 +10,6 @@ documentation = "https://docs.rs/eyre" readme = "README.md" categories = ["rust-patterns"] -[features] -default = ["std"] -std = [] - [dev-dependencies] futures = { version = "0.3", default-features = false } rustversion = "1.0" @@ -24,6 +20,7 @@ anyhow = "1.0.28" [dependencies] indenter = "0.3.0" +once_cell = "1.4.0" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/README.md b/README.md index fceb177..dac1ebc 100644 --- a/README.md +++ b/README.md @@ -12,21 +12,16 @@ This library provides [`eyre::Report`][Report], a trait object based error handling type for easy idiomatic error handling and reporting in Rust applications. -This crate is a fork of [`anyhow`] by @dtolnay with a support for customized -`Reports`. For more details on customization checkout the docs on +This crate is a fork of [`anyhow`] with a support for customized +error reports. For more details on customization checkout the docs on [`eyre::EyreHandler`]. -```toml -[dependencies] -eyre = "0.5" -``` - ## Custom Report Handlers -The heart of this crate is its ability to swap out the Handler type to change +The heart of this crate is it's ability to swap out the Handler type to change what information is carried alongside errors and how the end report is formatted. This crate is meant to be used alongside companion crates that -customize its behavior. Below is a list of known crates that export report +customize it's behavior. Below is a list of known crates that export report handlers for eyre and short summaries of what features they provide. - [`stable-eyre`]: Switches the backtrace type from `std`'s to `backtrace-rs`'s @@ -36,11 +31,11 @@ handlers for eyre and short summaries of what features they provide. `tracing_error::SpanTrace`. Provides a `Help` trait for attaching warnings and suggestions to error reports. The end report is then pretty printed with the help of [`color-backtrace`], [`color-spantrace`], and `ansi_term`. Check - out the README on [`color-eyre`] for screenshots of the report format. + out the README on [`color-eyre`] for details on the report format. - [`simple-eyre`]: A minimal `EyreHandler` that captures no additional information, for when you do not wish to capture `Backtrace`s with errors. -- [`jane-eyre`]: A a report handler crate that exists purely for the pun. -Currently just re-exports `color-eyre`. +- [`jane-eyre`]: A report handler crate that exists purely for the pun. + Currently just re-exports `color-eyre`. ## Details @@ -153,7 +148,7 @@ Cargo.toml. A global allocator is required. ```toml [dependencies] -eyre = { version = "0.5", default-features = false } +eyre = { version = "0.6", default-features = false } ``` Since the `?`-based error conversions would normally rely on the @@ -172,10 +167,11 @@ the necessary improvements for this to be possible as part of [RFC 2504]. ## Comparison to thiserror -Use Eyre if you don't care what error type your functions return, you just -want it to be easy. This is common in application code. Use [thiserror] if you -are a library that wants to design your own dedicated error type(s) so that on -failures the caller gets exactly the information that you choose. +Use `eyre` if you don't think you'll do anything with an error other than +report it. This is common in application code. Use `thiserror` if you think +you need an error type that can be handled via match or reported. This is +common in library crates where you don't know how your users will handle +your errors. [thiserror]: https://github.com/dtolnay/thiserror @@ -183,40 +179,7 @@ failures the caller gets exactly the information that you choose. This crate does its best to be usable as a drop in replacement of `anyhow` and vice-versa by `re-exporting` all of the renamed APIs with the names used in -`anyhow`. - -There are two main incompatibilities that you might encounter when porting a -codebase from `anyhow` to `eyre`: - -- type inference errors when using `eyre!` -- `.context` not being implemented for `Option` - -#### Type Inference Errors - -The type inference issue is caused by the generic parameter, which isn't -present in `anyhow::Error`. Specifically, the following works in anyhow: - -```rust -use anyhow::anyhow; - -// Works -let val = get_optional_val().ok_or_else(|| anyhow!("failed to get value")).unwrap_err(); -``` - -Where as with `eyre!` this will fail due to being unable to infer the type for -the Handler parameter. The solution to this problem, should you encounter it, -is to give the compiler a hint for what type it should be resolving to, either -via your return type or a type annotation. - -```rust,compile_fail -use eyre::eyre; - -// Broken -let val = get_optional_val().ok_or_else(|| eyre!("failed to get value")).unwrap(); - -// Works -let val: Report = get_optional_val().ok_or_else(|| eyre!("failed to get value")).unwrap(); -``` +`anyhow`, though there are some differences still. #### `Context` and `Option` @@ -246,7 +209,7 @@ let opt: Option<()> = None; let result: Result<()> = opt.ok_or_else(|| eyre!("new error message")); ``` -However, to help with porting we do provide a `ContextCompat` trait which +**NOTE**: However, to help with porting we do provide a `ContextCompat` trait which implements `context` for options which you can import to make existing `.context` calls compile. diff --git a/build.rs b/build.rs index f704a7b..8e1244e 100644 --- a/build.rs +++ b/build.rs @@ -35,9 +35,6 @@ const PROBE: &str = r#" "#; fn main() { - if !cfg!(feature = "std") { - return; - } match compile_probe() { Some(status) if status.success() => println!("cargo:rustc-cfg=backtrace"), _ => {} diff --git a/examples/custom_handler.rs b/examples/custom_handler.rs new file mode 100644 index 0000000..dfc90e1 --- /dev/null +++ b/examples/custom_handler.rs @@ -0,0 +1,81 @@ +use backtrace::Backtrace; +use eyre::EyreHandler; +use std::error::Error; +use std::{fmt, iter}; + +fn main() -> eyre::Result<()> { + // Install our custom eyre report hook for constructing our custom Handlers + install().unwrap(); + + // construct a report with, hopefully, our custom handler! + let mut report = eyre::eyre!("hello from custom error town!"); + + // manually set the custom msg for this report after it has been constructed + if let Some(handler) = report.handler_mut().downcast_mut::() { + handler.custom_msg = Some("you're the best users, you know that right???"); + } + + // print that shit!! + Err(report) +} + +// define a handler that captures backtraces unless told not to +fn install() -> Result<(), impl Error> { + let capture_backtrace = std::env::var("RUST_BACKWARDS_TRACE") + .map(|val| val != "0") + .unwrap_or(true); + + let hook = Hook { capture_backtrace }; + + eyre::set_hook(Box::new(move |e| Box::new(hook.make_handler(e)))) +} + +struct Hook { + capture_backtrace: bool, +} + +impl Hook { + fn make_handler(&self, _error: &(dyn Error + 'static)) -> Handler { + let backtrace = if self.capture_backtrace { + Some(Backtrace::new()) + } else { + None + }; + + Handler { + backtrace, + custom_msg: None, + } + } +} + +struct Handler { + // custom configured backtrace capture + backtrace: Option, + // customizable message payload associated with reports + custom_msg: Option<&'static str>, +} + +impl EyreHandler for Handler { + fn debug(&self, error: &(dyn Error + 'static), f: &mut fmt::Formatter<'_>) -> fmt::Result { + if f.alternate() { + return fmt::Debug::fmt(error, f); + } + + let errors = iter::successors(Some(error), |error| error.source()); + + for (ind, error) in errors.enumerate() { + write!(f, "\n{:>4}: {}", ind, error)?; + } + + if let Some(backtrace) = self.backtrace.as_ref() { + writeln!(f, "\n\nBacktrace:\n{:?}", backtrace)?; + } + + if let Some(msg) = self.custom_msg.as_ref() { + writeln!(f, "\n\n{}", msg)?; + } + + Ok(()) + } +} diff --git a/src/chain.rs b/src/chain.rs index 67edcc0..78131f2 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -1,23 +1,15 @@ use self::ChainState::*; use crate::StdError; -#[cfg(feature = "std")] use std::vec; -#[cfg(feature = "std")] pub(crate) use crate::Chain; -#[cfg(not(feature = "std"))] -pub(crate) struct Chain<'a> { - state: ChainState<'a>, -} - #[derive(Clone)] pub(crate) enum ChainState<'a> { Linked { next: Option<&'a (dyn StdError + 'static)>, }, - #[cfg(feature = "std")] Buffered { rest: vec::IntoIter<&'a (dyn StdError + 'static)>, }, @@ -61,7 +53,6 @@ impl<'a> Iterator for Chain<'a> { *next = error.source(); Some(error) } - #[cfg(feature = "std")] Buffered { rest } => rest.next(), } } @@ -72,7 +63,6 @@ impl<'a> Iterator for Chain<'a> { } } -#[cfg(feature = "std")] impl DoubleEndedIterator for Chain<'_> { fn next_back(&mut self) -> Option { match &mut self.state { @@ -103,13 +93,11 @@ impl ExactSizeIterator for Chain<'_> { } len } - #[cfg(feature = "std")] Buffered { rest } => rest.len(), } } } -#[cfg(feature = "std")] impl Default for Chain<'_> { fn default() -> Self { Chain { diff --git a/src/context.rs b/src/context.rs index 608682f..6c4a55c 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,5 +1,5 @@ use crate::error::ContextError; -use crate::{ContextCompat, EyreHandler, Report, StdError, WrapErr}; +use crate::{ContextCompat, Report, StdError, WrapErr}; use core::fmt::{self, Debug, Display, Write}; #[cfg(backtrace)] @@ -8,22 +8,17 @@ use std::backtrace::Backtrace; mod ext { use super::*; - pub trait StdError - where - H: EyreHandler, - { - fn ext_report(self, msg: D) -> Report + pub trait StdError { + fn ext_report(self, msg: D) -> Report where D: Display + Send + Sync + 'static; } - #[cfg(feature = "std")] - impl StdError for E + impl StdError for E where - H: EyreHandler, E: std::error::Error + Send + Sync + 'static, { - fn ext_report(self, msg: D) -> Report + fn ext_report(self, msg: D) -> Report where D: Display + Send + Sync + 'static, { @@ -31,11 +26,8 @@ mod ext { } } - impl StdError for Report - where - H: EyreHandler, - { - fn ext_report(self, msg: D) -> Report + impl StdError for Report { + fn ext_report(self, msg: D) -> Report where D: Display + Send + Sync + 'static, { @@ -44,19 +36,18 @@ mod ext { } } -impl WrapErr for Result +impl WrapErr for Result where - H: EyreHandler, - E: ext::StdError + Send + Sync + 'static, + E: ext::StdError + Send + Sync + 'static, { - fn wrap_err(self, msg: D) -> Result> + fn wrap_err(self, msg: D) -> Result where D: Display + Send + Sync + 'static, { self.map_err(|error| error.ext_report(msg)) } - fn wrap_err_with(self, msg: F) -> Result> + fn wrap_err_with(self, msg: F) -> Result where D: Display + Send + Sync + 'static, F: FnOnce() -> D, @@ -64,14 +55,14 @@ where self.map_err(|error| error.ext_report(msg())) } - fn context(self, msg: D) -> Result> + fn context(self, msg: D) -> Result where D: Display + Send + Sync + 'static, { self.wrap_err(msg) } - fn with_context(self, msg: F) -> Result> + fn with_context(self, msg: F) -> Result where D: Display + Send + Sync + 'static, F: FnOnce() -> D, @@ -80,18 +71,15 @@ where } } -impl ContextCompat for Option -where - H: EyreHandler, -{ - fn wrap_err(self, msg: D) -> Result> +impl ContextCompat for Option { + fn wrap_err(self, msg: D) -> Result where D: Display + Send + Sync + 'static, { self.context(msg) } - fn wrap_err_with(self, msg: F) -> Result> + fn wrap_err_with(self, msg: F) -> Result where D: Display + Send + Sync + 'static, F: FnOnce() -> D, @@ -99,14 +87,14 @@ where self.with_context(msg) } - fn context(self, msg: D) -> Result> + fn context(self, msg: D) -> Result where D: Display + Send + Sync + 'static, { self.ok_or_else(|| Report::from_display(msg)) } - fn with_context(self, msg: F) -> Result> + fn with_context(self, msg: F) -> Result where D: Display + Send + Sync + 'static, F: FnOnce() -> D, @@ -152,9 +140,8 @@ where } } -impl StdError for ContextError> +impl StdError for ContextError where - H: EyreHandler, D: Display, { fn source(&self) -> Option<&(dyn StdError + 'static)> { @@ -185,8 +172,8 @@ impl Write for Quoted<&mut fmt::Formatter<'_>> { pub(crate) mod private { use super::*; - pub trait Sealed {} + pub trait Sealed {} - impl Sealed for Result where E: ext::StdError {} - impl Sealed for Option {} + impl Sealed for Result where E: ext::StdError {} + impl Sealed for Option {} } diff --git a/src/error.rs b/src/error.rs index 029843c..721217b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,3 @@ -use crate::alloc::Box; use crate::chain::Chain; use crate::EyreHandler; use crate::{Report, StdError}; @@ -7,13 +6,9 @@ use core::fmt::{self, Debug, Display}; use core::mem::{self, ManuallyDrop}; use core::ptr::{self, NonNull}; -#[cfg(feature = "std")] use core::ops::{Deref, DerefMut}; -impl Report -where - H: EyreHandler, -{ +impl Report { /// Create a new error object from any error type. /// /// The error type must be threadsafe and `'static`, so that the `Report` @@ -21,8 +16,6 @@ where /// /// If the error type does not provide a backtrace, a backtrace will be /// created here to ensure that a backtrace exists. - #[cfg(feature = "std")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] pub fn new(error: E) -> Self where E: StdError + Send + Sync + 'static, @@ -74,23 +67,22 @@ where Report::from_adhoc(message) } - #[cfg(feature = "std")] pub(crate) fn from_std(error: E) -> Self where E: StdError + Send + Sync + 'static, { let vtable = &ErrorVTable { - object_drop: object_drop::, - object_ref: object_ref::, - #[cfg(feature = "std")] - object_mut: object_mut::, - object_boxed: object_boxed::, - object_downcast: object_downcast::, - object_drop_rest: object_drop_front::, + object_drop: object_drop::, + object_ref: object_ref::, + object_mut: object_mut::, + object_boxed: object_boxed::, + object_downcast: object_downcast::, + object_drop_rest: object_drop_front::, }; // Safety: passing vtable that operates on the right type E. - let handler = Some(H::default(&error)); + let handler = Some(crate::capture_handler(&error)); + unsafe { Report::construct(error, vtable, handler) } } @@ -101,18 +93,18 @@ where use crate::wrapper::MessageError; let error: MessageError = MessageError(message); let vtable = &ErrorVTable { - object_drop: object_drop::, H>, - object_ref: object_ref::, H>, - #[cfg(feature = "std")] - object_mut: object_mut::, H>, - object_boxed: object_boxed::, H>, - object_downcast: object_downcast::, - object_drop_rest: object_drop_front::, + object_drop: object_drop::>, + object_ref: object_ref::>, + object_mut: object_mut::>, + object_boxed: object_boxed::>, + object_downcast: object_downcast::, + object_drop_rest: object_drop_front::, }; // Safety: MessageError is repr(transparent) so it is okay for the // vtable to allow casting the MessageError to M. - let handler = Some(H::default(&error)); + let handler = Some(crate::capture_handler(&error)); + unsafe { Report::construct(error, vtable, handler) } } @@ -123,22 +115,21 @@ where use crate::wrapper::{DisplayError, NoneError}; let error: DisplayError = DisplayError(message); let vtable = &ErrorVTable { - object_drop: object_drop::, H>, - object_ref: object_ref::, H>, - #[cfg(feature = "std")] - object_mut: object_mut::, H>, - object_boxed: object_boxed::, H>, - object_downcast: object_downcast::, - object_drop_rest: object_drop_front::, + object_drop: object_drop::>, + object_ref: object_ref::>, + object_mut: object_mut::>, + object_boxed: object_boxed::>, + object_downcast: object_downcast::, + object_drop_rest: object_drop_front::, }; // Safety: DisplayError is repr(transparent) so it is okay for the // vtable to allow casting the DisplayError to M. - let handler = Some(H::default(&NoneError)); + let handler = Some(crate::capture_handler(&NoneError)); + unsafe { Report::construct(error, vtable, handler) } } - #[cfg(feature = "std")] pub(crate) fn from_msg(msg: D, error: E) -> Self where D: Display + Send + Sync + 'static, @@ -147,33 +138,32 @@ where let error: ContextError = ContextError { msg, error }; let vtable = &ErrorVTable { - object_drop: object_drop::, H>, - object_ref: object_ref::, H>, - #[cfg(feature = "std")] - object_mut: object_mut::, H>, - object_boxed: object_boxed::, H>, - object_downcast: context_downcast::, - object_drop_rest: context_drop_rest::, + object_drop: object_drop::>, + object_ref: object_ref::>, + object_mut: object_mut::>, + object_boxed: object_boxed::>, + object_downcast: context_downcast::, + object_drop_rest: context_drop_rest::, }; // Safety: passing vtable that operates on the right type. - let handler = Some(H::default(&error)); + let handler = Some(crate::capture_handler(&error)); + unsafe { Report::construct(error, vtable, handler) } } - #[cfg(feature = "std")] pub(crate) fn from_boxed(error: Box) -> Self { use crate::wrapper::BoxedError; let error = BoxedError(error); - let handler = Some(H::default(&error)); + let handler = Some(crate::capture_handler(&error)); + let vtable = &ErrorVTable { - object_drop: object_drop::, - object_ref: object_ref::, - #[cfg(feature = "std")] - object_mut: object_mut::, - object_boxed: object_boxed::, - object_downcast: object_downcast::, H>, - object_drop_rest: object_drop_front::, H>, + object_drop: object_drop::, + object_ref: object_ref::, + object_mut: object_mut::, + object_boxed: object_boxed::, + object_downcast: object_downcast::>, + object_drop_rest: object_drop_front::>, }; // Safety: BoxedError is repr(transparent) so it is okay for the vtable @@ -186,7 +176,11 @@ where // // Unsafe because the given vtable must have sensible behavior on the error // value of type E. - unsafe fn construct(error: E, vtable: &'static ErrorVTable, handler: Option) -> Self + unsafe fn construct( + error: E, + vtable: &'static ErrorVTable, + handler: Option>, + ) -> Self where E: StdError + Send + Sync + 'static, { @@ -201,7 +195,7 @@ where // result is a thin pointer. The necessary behavior for manipulating the // underlying ErrorImpl is preserved in the vtable provided by the // caller rather than a builtin fat pointer vtable. - let erased = mem::transmute::>, Box>>(inner); + let erased = mem::transmute::>, Box>>(inner); let inner = ManuallyDrop::new(erased); Report { inner } } @@ -264,16 +258,15 @@ where D: Display + Send + Sync + 'static, { let handler = self.inner.handler.take(); - let error: ContextError> = ContextError { msg, error: self }; + let error: ContextError = ContextError { msg, error: self }; let vtable = &ErrorVTable { - object_drop: object_drop::>, H>, - object_ref: object_ref::>, H>, - #[cfg(feature = "std")] - object_mut: object_mut::>, H>, - object_boxed: object_boxed::>, H>, - object_downcast: context_chain_downcast::, - object_drop_rest: context_chain_drop_rest::, + object_drop: object_drop::>, + object_ref: object_ref::>, + object_mut: object_mut::>, + object_boxed: object_boxed::>, + object_downcast: context_chain_downcast::, + object_drop_rest: context_chain_drop_rest::, }; // Safety: passing vtable that operates on the right type. @@ -301,7 +294,6 @@ where /// None /// } /// ``` - #[cfg(feature = "std")] pub fn chain(&self) -> Chain<'_> { self.inner.chain() } @@ -311,7 +303,6 @@ where /// /// The root cause is the last error in the iterator produced by /// [`chain()`][Report::chain]. - #[cfg(feature = "std")] pub fn root_cause(&self) -> &(dyn StdError + 'static) { let mut chain = self.chain(); let mut root_cause = chain.next().unwrap(); @@ -432,32 +423,30 @@ where } /// Get a reference to the Handler for this Report. - pub fn handler(&self) -> &H { - self.inner.handler.as_ref().unwrap() + pub fn handler(&self) -> &dyn EyreHandler { + self.inner.handler.as_ref().unwrap().as_ref() } /// Get a mutable reference to the Handler for this Report. - pub fn handler_mut(&mut self) -> &mut H { - self.inner.handler.as_mut().unwrap() + pub fn handler_mut(&mut self) -> &mut dyn EyreHandler { + self.inner.handler.as_mut().unwrap().as_mut() } /// Get a reference to the Handler for this Report. #[doc(hidden)] - pub fn context(&self) -> &H { - self.inner.handler.as_ref().unwrap() + pub fn context(&self) -> &dyn EyreHandler { + self.inner.handler.as_ref().unwrap().as_ref() } /// Get a mutable reference to the Handler for this Report. #[doc(hidden)] - pub fn context_mut(&mut self) -> &mut H { - self.inner.handler.as_mut().unwrap() + pub fn context_mut(&mut self) -> &mut dyn EyreHandler { + self.inner.handler.as_mut().unwrap().as_mut() } } -#[cfg(feature = "std")] -impl From for Report +impl From for Report where - H: EyreHandler, E: StdError + Send + Sync + 'static, { fn from(error: E) -> Self { @@ -465,8 +454,7 @@ where } } -#[cfg(feature = "std")] -impl Deref for Report { +impl Deref for Report { type Target = dyn StdError + Send + Sync + 'static; fn deref(&self) -> &Self::Target { @@ -474,29 +462,25 @@ impl Deref for Report { } } -#[cfg(feature = "std")] -impl DerefMut for Report { +impl DerefMut for Report { fn deref_mut(&mut self) -> &mut Self::Target { self.inner.error_mut() } } -impl Display for Report { +impl Display for Report { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { self.inner.display(formatter) } } -impl Debug for Report { +impl Debug for Report { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { self.inner.debug(formatter) } } -impl Drop for Report -where - H: EyreHandler, -{ +impl Drop for Report { fn drop(&mut self) { unsafe { // Read Box> from self. @@ -509,86 +493,70 @@ where } } -struct ErrorVTable -where - H: EyreHandler, -{ - object_drop: unsafe fn(Box>), - object_ref: unsafe fn(&ErrorImpl<(), H>) -> &(dyn StdError + Send + Sync + 'static), - #[cfg(feature = "std")] - object_mut: unsafe fn(&mut ErrorImpl<(), H>) -> &mut (dyn StdError + Send + Sync + 'static), +struct ErrorVTable { + object_drop: unsafe fn(Box>), + object_ref: unsafe fn(&ErrorImpl<()>) -> &(dyn StdError + Send + Sync + 'static), + object_mut: unsafe fn(&mut ErrorImpl<()>) -> &mut (dyn StdError + Send + Sync + 'static), #[allow(clippy::type_complexity)] - object_boxed: unsafe fn(Box>) -> Box, - object_downcast: unsafe fn(&ErrorImpl<(), H>, TypeId) -> Option>, - object_drop_rest: unsafe fn(Box>, TypeId), + object_boxed: unsafe fn(Box>) -> Box, + object_downcast: unsafe fn(&ErrorImpl<()>, TypeId) -> Option>, + object_drop_rest: unsafe fn(Box>, TypeId), } // Safety: requires layout of *e to match ErrorImpl. -unsafe fn object_drop(e: Box>) -where - H: EyreHandler, -{ +unsafe fn object_drop(e: Box>) { // Cast back to ErrorImpl so that the allocator receives the correct // Layout to deallocate the Box's memory. - let unerased = mem::transmute::>, Box>>(e); + let unerased = mem::transmute::>, Box>>(e); drop(unerased); } // Safety: requires layout of *e to match ErrorImpl. -unsafe fn object_drop_front(e: Box>, target: TypeId) -where - H: EyreHandler, -{ +unsafe fn object_drop_front(e: Box>, target: TypeId) { // Drop the fields of ErrorImpl other than E as well as the Box allocation, // without dropping E itself. This is used by downcast after doing a // ptr::read to take ownership of the E. let _ = target; - let unerased = mem::transmute::>, Box, H>>>(e); + let unerased = mem::transmute::>, Box>>>(e); drop(unerased); } // Safety: requires layout of *e to match ErrorImpl. -unsafe fn object_ref(e: &ErrorImpl<(), H>) -> &(dyn StdError + Send + Sync + 'static) +unsafe fn object_ref(e: &ErrorImpl<()>) -> &(dyn StdError + Send + Sync + 'static) where - H: EyreHandler, E: StdError + Send + Sync + 'static, { // Attach E's native StdError vtable onto a pointer to self._object. - &(*(e as *const ErrorImpl<(), H> as *const ErrorImpl))._object + &(*(e as *const ErrorImpl<()> as *const ErrorImpl))._object } // Safety: requires layout of *e to match ErrorImpl. -#[cfg(feature = "std")] -unsafe fn object_mut(e: &mut ErrorImpl<(), H>) -> &mut (dyn StdError + Send + Sync + 'static) +unsafe fn object_mut(e: &mut ErrorImpl<()>) -> &mut (dyn StdError + Send + Sync + 'static) where - H: EyreHandler, E: StdError + Send + Sync + 'static, { // Attach E's native StdError vtable onto a pointer to self._object. - &mut (*(e as *mut ErrorImpl<(), H> as *mut ErrorImpl))._object + &mut (*(e as *mut ErrorImpl<()> as *mut ErrorImpl))._object } // Safety: requires layout of *e to match ErrorImpl. -unsafe fn object_boxed(e: Box>) -> Box +unsafe fn object_boxed(e: Box>) -> Box where - H: EyreHandler, E: StdError + Send + Sync + 'static, - H: Send + Sync + 'static, { // Attach ErrorImpl's native StdError vtable. The StdError impl is below. - mem::transmute::>, Box>>(e) + mem::transmute::>, Box>>(e) } // Safety: requires layout of *e to match ErrorImpl. -unsafe fn object_downcast(e: &ErrorImpl<(), H>, target: TypeId) -> Option> +unsafe fn object_downcast(e: &ErrorImpl<()>, target: TypeId) -> Option> where - H: EyreHandler, E: 'static, { if TypeId::of::() == target { // Caller is looking for an E pointer and e is ErrorImpl, take a // pointer to its E field. - let unerased = e as *const ErrorImpl<(), H> as *const ErrorImpl; + let unerased = e as *const ErrorImpl<()> as *const ErrorImpl; let addr = &(*unerased)._object as *const E as *mut (); Some(NonNull::new_unchecked(addr)) } else { @@ -597,19 +565,17 @@ where } // Safety: requires layout of *e to match ErrorImpl>. -#[cfg(feature = "std")] -unsafe fn context_downcast(e: &ErrorImpl<(), H>, target: TypeId) -> Option> +unsafe fn context_downcast(e: &ErrorImpl<()>, target: TypeId) -> Option> where - H: EyreHandler, D: 'static, E: 'static, { if TypeId::of::() == target { - let unerased = e as *const ErrorImpl<(), H> as *const ErrorImpl, H>; + let unerased = e as *const ErrorImpl<()> as *const ErrorImpl>; let addr = &(*unerased)._object.msg as *const D as *mut (); Some(NonNull::new_unchecked(addr)) } else if TypeId::of::() == target { - let unerased = e as *const ErrorImpl<(), H> as *const ErrorImpl, H>; + let unerased = e as *const ErrorImpl<()> as *const ErrorImpl>; let addr = &(*unerased)._object.error as *const E as *mut (); Some(NonNull::new_unchecked(addr)) } else { @@ -618,10 +584,8 @@ where } // Safety: requires layout of *e to match ErrorImpl>. -#[cfg(feature = "std")] -unsafe fn context_drop_rest(e: Box>, target: TypeId) +unsafe fn context_drop_rest(e: Box>, target: TypeId) where - H: EyreHandler, D: 'static, E: 'static, { @@ -629,58 +593,54 @@ where // ptr::read to take ownership of that value. if TypeId::of::() == target { let unerased = mem::transmute::< - Box>, - Box, E>, H>>, + Box>, + Box, E>>>, >(e); drop(unerased); } else { let unerased = mem::transmute::< - Box>, - Box>, H>>, + Box>, + Box>>>, >(e); drop(unerased); } } // Safety: requires layout of *e to match ErrorImpl>. -unsafe fn context_chain_downcast(e: &ErrorImpl<(), H>, target: TypeId) -> Option> +unsafe fn context_chain_downcast(e: &ErrorImpl<()>, target: TypeId) -> Option> where - H: EyreHandler, D: 'static, { if TypeId::of::() == target { - let unerased = - e as *const ErrorImpl<(), H> as *const ErrorImpl>, H>; + let unerased = e as *const ErrorImpl<()> as *const ErrorImpl>; let addr = &(*unerased)._object.msg as *const D as *mut (); Some(NonNull::new_unchecked(addr)) } else { // Recurse down the context chain per the inner error's vtable. - let unerased = - e as *const ErrorImpl<(), H> as *const ErrorImpl>, H>; + let unerased = e as *const ErrorImpl<()> as *const ErrorImpl>; let source = &(*unerased)._object.error; (source.inner.vtable.object_downcast)(&source.inner, target) } } // Safety: requires layout of *e to match ErrorImpl>. -unsafe fn context_chain_drop_rest(e: Box>, target: TypeId) +unsafe fn context_chain_drop_rest(e: Box>, target: TypeId) where - H: EyreHandler, D: 'static, { // Called after downcasting by value to either the D or one of the causes // and doing a ptr::read to take ownership of that value. if TypeId::of::() == target { let unerased = mem::transmute::< - Box>, - Box, Report>, H>>, + Box>, + Box, Report>>>, >(e); // Drop the entire rest of the data structure rooted in the next Report. drop(unerased); } else { let unerased = mem::transmute::< - Box>, - Box>>, H>>, + Box>, + Box>>>, >(e); // Read out a ManuallyDrop>> from the next error. let inner = ptr::read(&unerased._object.error.inner); @@ -693,12 +653,9 @@ where // repr C to ensure that E remains in the final position. #[repr(C)] -pub(crate) struct ErrorImpl -where - H: EyreHandler, -{ - vtable: &'static ErrorVTable, - pub(crate) handler: Option, +pub(crate) struct ErrorImpl { + vtable: &'static ErrorVTable, + pub(crate) handler: Option>, // NOTE: Don't use directly. Use only through vtable. Erased type may have // different alignment. _object: E, @@ -712,29 +669,22 @@ pub(crate) struct ContextError { pub(crate) error: E, } -impl ErrorImpl -where - H: EyreHandler, -{ - fn erase(&self) -> &ErrorImpl<(), H> { +impl ErrorImpl { + fn erase(&self) -> &ErrorImpl<()> { // Erase the concrete type of E but preserve the vtable in self.vtable // for manipulating the resulting thin pointer. This is analogous to an // unsize coersion. - unsafe { &*(self as *const ErrorImpl as *const ErrorImpl<(), H>) } + unsafe { &*(self as *const ErrorImpl as *const ErrorImpl<()>) } } } -impl ErrorImpl<(), H> -where - H: EyreHandler, -{ +impl ErrorImpl<()> { pub(crate) fn error(&self) -> &(dyn StdError + Send + Sync + 'static) { // Use vtable to attach E's native StdError vtable for the right // original type E. unsafe { &*(self.vtable.object_ref)(self) } } - #[cfg(feature = "std")] pub(crate) fn error_mut(&mut self) -> &mut (dyn StdError + Send + Sync + 'static) { // Use vtable to attach E's native StdError vtable for the right // original type E. @@ -746,9 +696,8 @@ where } } -impl StdError for ErrorImpl +impl StdError for ErrorImpl where - H: EyreHandler, E: StdError, { fn source(&self) -> Option<&(dyn StdError + 'static)> { @@ -756,9 +705,8 @@ where } } -impl Debug for ErrorImpl +impl Debug for ErrorImpl where - H: EyreHandler, E: Debug, { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -766,9 +714,8 @@ where } } -impl Display for ErrorImpl +impl Display for ErrorImpl where - H: EyreHandler, E: Display, { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -776,8 +723,8 @@ where } } -impl From> for Box { - fn from(error: Report) -> Self { +impl From for Box { + fn from(error: Report) -> Self { let outer = ManuallyDrop::new(error); unsafe { // Read Box> from error. Can't move it out because @@ -792,21 +739,19 @@ impl From> for Box From> for Box { - fn from(error: Report) -> Self { +impl From for Box { + fn from(error: Report) -> Self { Box::::from(error) } } -#[cfg(feature = "std")] -impl AsRef for Report { +impl AsRef for Report { fn as_ref(&self) -> &(dyn StdError + Send + Sync + 'static) { &**self } } -#[cfg(feature = "std")] -impl AsRef for Report { +impl AsRef for Report { fn as_ref(&self) -> &(dyn StdError + 'static) { &**self } diff --git a/src/fmt.rs b/src/fmt.rs index a656c78..b4e97a3 100644 --- a/src/fmt.rs +++ b/src/fmt.rs @@ -1,11 +1,7 @@ use crate::error::ErrorImpl; -use crate::EyreHandler; use core::fmt; -impl ErrorImpl<(), H> -where - H: EyreHandler, -{ +impl ErrorImpl<()> { pub(crate) fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.handler .as_ref() diff --git a/src/kind.rs b/src/kind.rs index 1041236..7c1a92d 100644 --- a/src/kind.rs +++ b/src/kind.rs @@ -45,10 +45,9 @@ // let error = $msg; // (&error).eyre_kind().new(error) -use crate::{EyreHandler, Report}; +use crate::Report; use core::fmt::{Debug, Display}; -#[cfg(feature = "std")] use crate::StdError; pub struct Adhoc; @@ -63,7 +62,7 @@ pub trait AdhocKind: Sized { impl AdhocKind for &T where T: ?Sized + Display + Debug + Send + Sync + 'static {} impl Adhoc { - pub fn new(self, message: M) -> Report + pub fn new(self, message: M) -> Report where M: Display + Debug + Send + Sync + 'static, { @@ -71,36 +70,28 @@ impl Adhoc { } } -pub struct Trait(std::marker::PhantomData); +pub struct Trait; -pub trait TraitKind: Sized { +pub trait TraitKind: Sized { #[inline] - fn eyre_kind(&self) -> Trait { - Trait(std::marker::PhantomData) + fn eyre_kind(&self) -> Trait { + Trait } } -impl TraitKind for E -where - E: Into>, - H: EyreHandler, -{ -} +impl TraitKind for E where E: Into {} -impl Trait { - pub fn new(self, error: E) -> Report +impl Trait { + pub fn new(self, error: E) -> Report where - E: Into>, - H: EyreHandler, + E: Into, { error.into() } } -#[cfg(feature = "std")] pub struct Boxed; -#[cfg(feature = "std")] pub trait BoxedKind: Sized { #[inline] fn eyre_kind(&self) -> Boxed { @@ -108,47 +99,10 @@ pub trait BoxedKind: Sized { } } -#[cfg(feature = "std")] impl BoxedKind for Box {} -#[cfg(feature = "std")] impl Boxed { - pub fn new(self, error: Box) -> Report { + pub fn new(self, error: Box) -> Report { Report::from_boxed(error) } } - -#[cfg(test)] -mod test { - use super::*; - use crate::eyre; - use std::num::ParseIntError; - - struct NonDefaultHandler; - - impl EyreHandler for NonDefaultHandler { - #[allow(unused_variables)] - fn default(error: &(dyn StdError + 'static)) -> Self { - Self - } - - fn debug( - &self, - _error: &(dyn StdError + 'static), - _f: &mut core::fmt::Formatter<'_>, - ) -> core::fmt::Result { - Ok(()) - } - } - - fn _parse(s: &str) -> Result { - s.parse::() - } - - fn _throw_error() -> Result<(), Report> { - match _parse("abc") { - Ok(_) => Ok(()), - Err(e) => Err(eyre!(e).wrap_err("try parsing an actual number")), - } - } -} diff --git a/src/lib.rs b/src/lib.rs index ca053bf..3e1fbfc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,16 +2,16 @@ //! error handling type for easy idiomatic error handling and reporting in Rust //! applications. //! -//! This crate is a fork of [`anyhow`] by @dtolnay with a support for customized -//! `Reports`. For more details on customization checkout the docs on +//! This crate is a fork of [`anyhow`] with a support for customized +//! error reports. For more details on customization checkout the docs on //! [`eyre::EyreHandler`]. //! //! ## Custom Report Handlers //! -//! The heart of this crate is its ability to swap out the Handler type to change +//! The heart of this crate is it's ability to swap out the Handler type to change //! what information is carried alongside errors and how the end report is //! formatted. This crate is meant to be used alongside companion crates that -//! customize its behavior. Below is a list of known crates that export report +//! customize it's behavior. Below is a list of known crates that export report //! handlers for eyre and short summaries of what features they provide. //! //! - [`stable-eyre`]: Switches the backtrace type from `std`'s to `backtrace-rs`'s @@ -21,11 +21,11 @@ //! `tracing_error::SpanTrace`. Provides a `Help` trait for attaching warnings //! and suggestions to error reports. The end report is then pretty printed with //! the help of [`color-backtrace`], [`color-spantrace`], and `ansi_term`. Check -//! out the README on [`color-eyre`] for screenshots of the report format. +//! out the README on [`color-eyre`] for details on the report format. //! - [`simple-eyre`]: A minimal `EyreHandler` that captures no additional //! information, for when you do not wish to capture `Backtrace`s with errors. -//! - [`jane-eyre`]: A a report handler crate that exists purely for the pun. -//! Currently just re-exports `color-eyre`. +//! - [`jane-eyre`]: A report handler crate that exists purely for the pun. +//! Currently just re-exports `color-eyre`. //! //! ## Details //! @@ -206,7 +206,7 @@ //! //! ```toml //! [dependencies] -//! eyre = { version = "0.5", default-features = false } +//! eyre = { version = "0.6", default-features = false } //! ``` //! //! Since the `?`-based error conversions would normally rely on the @@ -225,10 +225,11 @@ //! //! ## Comparison to thiserror //! -//! Use Eyre if you don't care what error type your functions return, you just -//! want it to be easy. This is common in application code. Use [thiserror] if you -//! are a library that wants to design your own dedicated error type(s) so that on -//! failures the caller gets exactly the information that you choose. +//! Use `eyre` if you don't think you'll do anything with an error other than +//! report it. This is common in application code. Use `thiserror` if you think +//! you need an error type that can be handled via match or reported. This is +//! common in library crates where you don't know how your users will handle +//! your errors. //! //! [thiserror]: https://github.com/dtolnay/thiserror //! @@ -236,42 +237,7 @@ //! //! This crate does its best to be usable as a drop in replacement of `anyhow` and //! vice-versa by `re-exporting` all of the renamed APIs with the names used in -//! `anyhow`. -//! -//! There are two main incompatibilities that you might encounter when porting a -//! codebase from `anyhow` to `eyre`: -//! -//! - type inference errors when using `eyre!` -//! - `.context` not being implemented for `Option` -//! -//! #### Type Inference Errors -//! -//! The type inference issue is caused by the generic parameter, which isn't -//! present in `anyhow::Error`. Specifically, the following works in anyhow: -//! -//! ```rust -//! # fn get_optional_val() -> Option<()> { None }; -//! use anyhow::anyhow; -//! -//! // Works -//! let val = get_optional_val().ok_or_else(|| anyhow!("failed to get value")).unwrap_err(); -//! ``` -//! -//! Where as with `eyre!` this will fail due to being unable to infer the type for -//! the Handler parameter. The solution to this problem, should you encounter it, -//! is to give the compiler a hint for what type it should be resolving to, either -//! via your return type or a type annotation. -//! -//! ```rust,compile_fail -//! use eyre::eyre; -//! -//! # fn get_optional_val() -> Option<()> { None }; -//! // Broken -//! let val = get_optional_val().ok_or_else(|| eyre!("failed to get value")).unwrap(); -//! -//! // Works -//! let val: Report = get_optional_val().ok_or_else(|| eyre!("failed to get value")).unwrap(); -//! ``` +//! `anyhow`, though there are some differences still. //! //! #### `Context` and `Option` //! @@ -301,7 +267,7 @@ //! let result: Result<()> = opt.ok_or_else(|| eyre!("new error message")); //! ``` //! -//! However, to help with porting we do provide a `ContextCompat` trait which +//! **NOTE**: However, to help with porting we do provide a `ContextCompat` trait which //! implements `context` for options which you can import to make existing //! `.context` calls compile. //! @@ -317,7 +283,7 @@ //! [`simple-eyre`]: https://github.com/yaahc/simple-eyre //! [`color-spantrace`]: https://github.com/yaahc/color-spantrace //! [`color-backtrace`]: https://github.com/athre0z/color-backtrace -#![doc(html_root_url = "https://docs.rs/eyre/0.5.0")] +#![doc(html_root_url = "https://docs.rs/eyre/0.6.0")] #![warn( missing_debug_implementations, missing_docs, @@ -343,30 +309,12 @@ )] #![cfg_attr(backtrace, feature(backtrace))] #![cfg_attr(doc_cfg, feature(doc_cfg))] -#![cfg_attr(not(feature = "std"), no_std)] #![allow( clippy::needless_doctest_main, clippy::new_ret_no_self, clippy::wrong_self_convention )] -mod alloc { - #[cfg(not(feature = "std"))] - extern crate alloc; - - #[cfg(not(feature = "std"))] - pub(crate) use alloc::boxed::Box; - - #[cfg(feature = "std")] - pub(crate) use std::boxed::Box; - - #[cfg(not(feature = "std"))] - pub(crate) use alloc::string::String; - - // #[cfg(feature = "std")] - // pub(crate) use std::string::String; -} - #[macro_use] mod backtrace; mod chain; @@ -377,28 +325,17 @@ mod kind; mod macros; mod wrapper; -use crate::alloc::Box; use crate::backtrace::Backtrace; use crate::error::ErrorImpl; use core::fmt::Display; use core::mem::ManuallyDrop; -#[cfg(not(feature = "std"))] -use core::fmt::Debug; - -#[cfg(feature = "std")] use std::error::Error as StdError; -#[cfg(not(feature = "std"))] -pub trait StdError: Debug + Display { - fn source(&self) -> Option<&(dyn StdError + 'static)> { - None - } -} - pub use eyre as format_err; /// Compatibility re-export of `eyre` for interopt with `anyhow` pub use eyre as anyhow; +use once_cell::sync::OnceCell; #[doc(hidden)] pub use DefaultHandler as DefaultContext; #[doc(hidden)] @@ -478,151 +415,174 @@ pub use WrapErr as Context; /// ``` /// /// If none of the built-in representations are appropriate and you would prefer -/// to render the error and its cause chain yourself, it can be done something -/// like this: +/// to render the error and its cause chain yourself, it can be done by defining +/// your own [`EyreHandler`] and [`hook`] to use it. /// -/// ``` -/// use eyre::{WrapErr, Result}; +/// [`EyreHandler`]: trait.EyreHandler.html +/// [`hook`]: fn.set_hook.html +pub struct Report { + inner: ManuallyDrop>>, +} + +type ErrorHook = + Box Box + Sync + Send + 'static>; + +static HOOK: OnceCell = OnceCell::new(); + +/// Error indicating that `set_hook` was unable to install the provided ErrorHook +#[derive(Debug)] +pub struct InstallError; + +impl core::fmt::Display for InstallError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str("cannot install provided ErrorHook, a hook has already been installed") + } +} + +impl StdError for InstallError {} + +/// Install the provided error hook for constructing EyreHandlers when converting +/// Errors to Reports /// -/// fn main() { -/// if let Err(err) = try_main() { -/// eprintln!("ERROR: {}", err); -/// err.chain().skip(1).for_each(|cause| eprintln!("because: {}", cause)); -/// std::process::exit(1); +/// # Details +/// +/// To customize the format and content of error reports from `eyre` you must +/// first define a new `EyreHandler` type to capture and store the extra context +/// and to define the format of how to display the chain of errors and this +/// stored context. Once this type has been defined you must also define a global +/// hook used to construct these handlers whenever `Report`s are constructed. +/// +/// # Examples +/// +/// ```rust,should_panic +/// use backtrace::Backtrace; +/// use eyre::EyreHandler; +/// use std::error::Error; +/// use std::{fmt, iter}; +/// +/// fn main() -> eyre::Result<()> { +/// // Install our custom eyre report hook for constructing our custom Handlers +/// install().unwrap(); +/// +/// // construct a report with, hopefully, our custom handler! +/// let mut report = eyre::eyre!("hello from custom error town!"); +/// +/// // manually set the custom msg for this report after it has been constructed +/// if let Some(handler) = report.handler_mut().downcast_mut::() { +/// handler.custom_msg = Some("you're the best users, you know that right???"); +/// } +/// +/// // print that shit!! +/// Err(report) +/// } +/// +/// // define a handler that captures backtraces unless told not to +/// fn install() -> Result<(), impl Error> { +/// let capture_backtrace = std::env::var("RUST_BACKWARDS_TRACE") +/// .map(|val| val != "0") +/// .unwrap_or(true); +/// +/// let hook = Hook { capture_backtrace }; +/// +/// eyre::set_hook(Box::new(move |e| Box::new(hook.make_handler(e)))) +/// } +/// +/// struct Hook { +/// capture_backtrace: bool, +/// } +/// +/// impl Hook { +/// fn make_handler(&self, _error: &(dyn Error + 'static)) -> Handler { +/// let backtrace = if self.capture_backtrace { +/// Some(Backtrace::new()) +/// } else { +/// None +/// }; +/// +/// Handler { +/// backtrace, +/// custom_msg: None, +/// } /// } /// } /// -/// fn try_main() -> Result<()> { -/// # const IGNORE: &str = stringify! { -/// ... -/// # }; -/// # Ok(()) -/// } -/// ``` -pub struct Report -where - H: EyreHandler, -{ - inner: ManuallyDrop>>, -} - -/// Error Report Handler trait for customizing `eyre::Report` -/// -/// ## Customization -/// -/// In order to insert your own custom context and report format you must first -/// implement the `eyre::EyreHandler` trait. -/// -/// Once you've defined a custom Handler type you can use it throughout your -/// application by defining a type alias. -/// -/// ```rust -/// use backtrace::Backtrace; -/// use eyre::EyreHandler; -/// # use eyre::Chain; -/// # use std::error::Error; -/// use indenter::indented; -/// -/// pub struct Handler { -/// backtrace: Backtrace, +/// struct Handler { +/// // custom configured backtrace capture +/// backtrace: Option, +/// // customizable message payload associated with reports +/// custom_msg: Option<&'static str>, /// } /// /// impl EyreHandler for Handler { -/// // ... -/// # #[allow(unused_variables)] -/// # fn default(error: &(dyn Error + 'static)) -> Self { -/// # let backtrace = Backtrace::new(); -/// # Self { backtrace } -/// # } -/// # fn debug( -/// # &self, -/// # error: &(dyn Error + 'static), -/// # f: &mut core::fmt::Formatter<'_>, -/// # ) -> core::fmt::Result { -/// # use core::fmt::Write as _; -/// # if f.alternate() { -/// # return core::fmt::Debug::fmt(error, f); -/// # } -/// # write!(f, "{}", error)?; -/// # if let Some(cause) = error.source() { -/// # write!(f, "\n\nCaused by:")?; -/// # let multiple = cause.source().is_some(); -/// # for (n, error) in Chain::new(cause).enumerate() { -/// # writeln!(f)?; -/// # if multiple { -/// # write!(indented(f).ind(n), "{}", error)?; -/// # } else { -/// # write!(indented(f), "{}", error)?; -/// # } -/// # } -/// # } -/// # let backtrace = &self.backtrace; -/// # write!(f, "\n\nStack backtrace:\n{:?}", backtrace)?; -/// # Ok(()) -/// # } -/// } +/// fn debug(&self, error: &(dyn Error + 'static), f: &mut fmt::Formatter<'_>) -> fmt::Result { +/// if f.alternate() { +/// return fmt::Debug::fmt(error, f); +/// } /// -/// type Report = eyre::Report; -/// type Result> = core::result::Result; +/// let errors = iter::successors(Some(error), |error| error.source()); +/// +/// for (ind, error) in errors.enumerate() { +/// write!(f, "\n{:>4}: {}", ind, error)?; +/// } +/// +/// if let Some(backtrace) = self.backtrace.as_ref() { +/// writeln!(f, "\n\nBacktrace:\n{:?}", backtrace)?; +/// } +/// +/// if let Some(msg) = self.custom_msg.as_ref() { +/// writeln!(f, "\n\n{}", msg)?; +/// } +/// +/// Ok(()) +/// } +/// } /// ``` -pub trait EyreHandler: Sized + Send + Sync + 'static { - /// Default construct a `Handler` when constructing a `Report`. - /// - /// This method provides a reference to the error being wrapped to support conditional - /// capturing of context like `backtrace` depending on whether the source error already - /// captured one. - /// - /// # Example - /// - /// ```rust - /// use backtrace::Backtrace; - /// use eyre::EyreHandler; - /// # use eyre::Chain; - /// use std::error::Error; - /// - /// pub struct Handler { - /// backtrace: Backtrace, - /// } - /// - /// impl EyreHandler for Handler { - /// # #[allow(unused_variables)] - /// fn default(error: &(dyn Error + 'static)) -> Self { - /// let backtrace = Backtrace::new(); - /// - /// Self { backtrace } - /// } - /// - /// // ... - /// # fn debug( - /// # &self, - /// # error: &(dyn Error + 'static), - /// # f: &mut core::fmt::Formatter<'_>, - /// # ) -> core::fmt::Result { - /// # use core::fmt::Write as _; - /// # if f.alternate() { - /// # return core::fmt::Debug::fmt(error, f); - /// # } - /// # write!(f, "{}", error)?; - /// # if let Some(cause) = error.source() { - /// # write!(f, "\n\nCaused by:")?; - /// # let multiple = cause.source().is_some(); - /// # for (n, error) in Chain::new(cause).enumerate() { - /// # writeln!(f)?; - /// # if multiple { - /// # write!(indenter::indented(f).ind(n), "{}", error)?; - /// # } else { - /// # write!(indenter::indented(f), "{}", error)?; - /// # } - /// # } - /// # } - /// # let backtrace = &self.backtrace; - /// # write!(f, "\n\nStack backtrace:\n{:?}", backtrace)?; - /// # Ok(()) - /// # } - /// } - /// ``` - fn default(err: &(dyn StdError + 'static)) -> Self; +pub fn set_hook(hook: ErrorHook) -> Result<(), InstallError> { + HOOK.set(hook).map_err(|_| InstallError) +} +fn capture_handler(error: &(dyn StdError + 'static)) -> Box { + let hook = HOOK + .get_or_init(|| Box::new(DefaultHandler::default_with)) + .as_ref(); + + hook(error) +} + +impl dyn EyreHandler { + /// + pub fn is(&self) -> bool { + // Get `TypeId` of the type this function is instantiated with. + let t = core::any::TypeId::of::(); + + // Get `TypeId` of the type in the trait object (`self`). + let concrete = self.type_id(); + + // Compare both `TypeId`s on equality. + t == concrete + } + + /// + pub fn downcast_ref(&self) -> Option<&T> { + if self.is::() { + unsafe { Some(&*(self as *const dyn EyreHandler as *const T)) } + } else { + None + } + } + + /// + pub fn downcast_mut(&mut self) -> Option<&mut T> { + if self.is::() { + unsafe { Some(&mut *(self as *mut dyn EyreHandler as *mut T)) } + } else { + None + } + } +} + +/// Error Report Handler trait for customizing `eyre::Report` +pub trait EyreHandler: core::any::Any + Send + Sync { /// Define the report format /// /// Used to override the report format of `eyre::Report` @@ -641,13 +601,6 @@ pub trait EyreHandler: Sized + Send + Sync + 'static { /// } /// /// impl EyreHandler for Handler { - /// # #[allow(unused_variables)] - /// # fn default(error: &(dyn Error + 'static)) -> Self { - /// # let backtrace = Backtrace::new(); - /// # Self { backtrace } - /// # } - /// // ... - /// /// fn debug( /// &self, /// error: &(dyn Error + 'static), @@ -715,6 +668,15 @@ pub struct DefaultHandler { backtrace: Option, } +impl DefaultHandler { + #[allow(unused_variables)] + fn default_with(error: &(dyn StdError + 'static)) -> Box { + let backtrace = backtrace_if_absent!(error); + + Box::new(Self { backtrace }) + } +} + impl core::fmt::Debug for DefaultHandler { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("DefaultHandler") @@ -730,13 +692,6 @@ impl core::fmt::Debug for DefaultHandler { } impl EyreHandler for DefaultHandler { - #[allow(unused_variables)] - fn default(error: &(dyn StdError + 'static)) -> Self { - let backtrace = backtrace_if_absent!(error); - - Self { backtrace } - } - fn debug( &self, error: &(dyn StdError + 'static), @@ -800,14 +755,13 @@ impl EyreHandler for DefaultHandler { /// None /// } /// ``` -#[cfg(feature = "std")] #[derive(Clone)] #[allow(missing_debug_implementations)] pub struct Chain<'a> { state: crate::chain::ChainState<'a>, } -/// `Result` +/// type alias for `Result` /// /// This is a reasonable return type to use throughout your application but also for `fn main`; if /// you do, failures will be printed along with a backtrace if one was captured. @@ -855,7 +809,7 @@ pub struct Chain<'a> { /// Ok(()) /// } /// ``` -pub type Result> = core::result::Result; +pub type Result = core::result::Result; /// Provides the `wrap_err` method for `Result`. /// @@ -1022,29 +976,26 @@ pub type Result> = core::result::Result; /// # panic!("expected downcast to succeed"); /// } /// ``` -pub trait WrapErr: context::private::Sealed -where - H: EyreHandler, -{ +pub trait WrapErr: context::private::Sealed { /// Wrap the error value with a new adhoc error - fn wrap_err(self, msg: D) -> Result> + fn wrap_err(self, msg: D) -> Result where D: Display + Send + Sync + 'static; /// Wrap the error value with a new adhoc error that is evaluated lazily /// only once an error does occur. - fn wrap_err_with(self, f: F) -> Result> + fn wrap_err_with(self, f: F) -> Result where D: Display + Send + Sync + 'static, F: FnOnce() -> D; /// Compatibility re-export of wrap_err for interopt with `anyhow` - fn context(self, msg: D) -> Result> + fn context(self, msg: D) -> Result where D: Display + Send + Sync + 'static; /// Compatibility re-export of wrap_err_with for interopt with `anyhow` - fn with_context(self, f: F) -> Result> + fn with_context(self, f: F) -> Result where D: Display + Send + Sync + 'static, F: FnOnce() -> D; @@ -1093,30 +1044,27 @@ where /// .ok_or_else(|| eyre!("the thing wasnt in the list")) /// } /// ``` -pub trait ContextCompat: context::private::Sealed -where - H: EyreHandler, -{ +pub trait ContextCompat: context::private::Sealed { /// Compatibility version of `wrap_err` for creating new errors with new source on `Option` /// when porting from `anyhow` - fn context(self, msg: D) -> Result> + fn context(self, msg: D) -> Result where D: Display + Send + Sync + 'static; /// Compatibility version of `wrap_err_with` for creating new errors with new source on `Option` /// when porting from `anyhow` - fn with_context(self, f: F) -> Result> + fn with_context(self, f: F) -> Result where D: Display + Send + Sync + 'static, F: FnOnce() -> D; /// Compatibility re-export of `context` for porting from `anyhow` to `eyre` - fn wrap_err(self, msg: D) -> Result> + fn wrap_err(self, msg: D) -> Result where D: Display + Send + Sync + 'static; /// Compatibility re-export of `with_context` for porting from `anyhow` to `eyre` - fn wrap_err_with(self, f: F) -> Result> + fn wrap_err_with(self, f: F) -> Result where D: Display + Send + Sync + 'static, F: FnOnce() -> D; @@ -1125,25 +1073,20 @@ where // Not public API. Referenced by macro-generated code. #[doc(hidden)] pub mod private { - use crate::{EyreHandler, Report}; + use crate::Report; use core::fmt::{Debug, Display}; - // #[cfg(backtrace)] - // use std::backtrace::Backtrace; - pub use core::result::Result::Err; #[doc(hidden)] pub mod kind { pub use crate::kind::{AdhocKind, TraitKind}; - #[cfg(feature = "std")] pub use crate::kind::BoxedKind; } - pub fn new_adhoc(message: M) -> Report + pub fn new_adhoc(message: M) -> Report where - H: EyreHandler, M: Display + Debug + Send + Sync + 'static, { Report::from_adhoc(message) diff --git a/src/wrapper.rs b/src/wrapper.rs index 2b9bdf8..f0334e5 100644 --- a/src/wrapper.rs +++ b/src/wrapper.rs @@ -63,25 +63,21 @@ impl Display for NoneError { impl StdError for NoneError {} -#[cfg(feature = "std")] #[repr(transparent)] pub(crate) struct BoxedError(pub(crate) Box); -#[cfg(feature = "std")] impl Debug for BoxedError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { Debug::fmt(&self.0, f) } } -#[cfg(feature = "std")] impl Display for BoxedError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { Display::fmt(&self.0, f) } } -#[cfg(feature = "std")] impl StdError for BoxedError { #[cfg(backtrace)] fn backtrace(&self) -> Option<&crate::backtrace::Backtrace> { diff --git a/tests/test_downcast.rs b/tests/test_downcast.rs index a098f8a..f025907 100644 --- a/tests/test_downcast.rs +++ b/tests/test_downcast.rs @@ -3,7 +3,7 @@ mod drop; use self::common::*; use self::drop::{DetectDrop, Flag}; -use eyre::{DefaultContext, Report}; +use eyre::Report; use std::error::Error as StdError; use std::fmt::{self, Display}; use std::io; @@ -90,7 +90,7 @@ fn test_large_alignment() { impl StdError for LargeAlignedError {} - let error: Report = Report::new(LargeAlignedError("oh no!")); + let error = Report::new(LargeAlignedError("oh no!")); assert_eq!( "oh no!", error.downcast_ref::().unwrap().0 diff --git a/tests/test_repr.rs b/tests/test_repr.rs index 3f70d39..9e595bd 100644 --- a/tests/test_repr.rs +++ b/tests/test_repr.rs @@ -1,7 +1,6 @@ mod drop; use self::drop::{DetectDrop, Flag}; -use eyre::DefaultContext; use eyre::Report; use std::marker::Unpin; use std::mem; @@ -28,6 +27,6 @@ fn test_autotraits() { #[test] fn test_drop() { let has_dropped = Flag::new(); - drop(Report::::new(DetectDrop::new(&has_dropped))); + drop(Report::new(DetectDrop::new(&has_dropped))); assert!(has_dropped.get()); } diff --git a/tests/test_source.rs b/tests/test_source.rs index 6d976a0..d7079f2 100644 --- a/tests/test_source.rs +++ b/tests/test_source.rs @@ -33,7 +33,7 @@ fn test_literal_source() { #[test] fn test_variable_source() { let msg = "oh no!"; - let error: Report = eyre!(msg); + let error = eyre!(msg); assert!(error.source().is_none()); let msg = msg.to_owned(); diff --git a/tests/ui/no-impl.stderr b/tests/ui/no-impl.stderr index eb46001..b40c438 100644 --- a/tests/ui/no-impl.stderr +++ b/tests/ui/no-impl.stderr @@ -4,18 +4,18 @@ error[E0599]: no method named `eyre_kind` found for reference `&Error` in the cu 4 | struct Error; | ------------- | | - | doesn't satisfy `Error: eyre::kind::TraitKind<_>` - | doesn't satisfy `Error: std::convert::Into>` + | doesn't satisfy `Error: eyre::kind::TraitKind` + | doesn't satisfy `Error: std::convert::Into` | doesn't satisfy `Error: std::fmt::Display` ... 7 | let _ = eyre!(Error); | ^^^^^^^^^^^^ method not found in `&Error` | = note: the method `eyre_kind` exists but the following trait bounds were not satisfied: - `Error: std::convert::Into>` - which is required by `Error: eyre::kind::TraitKind<_>` + `Error: std::convert::Into` + which is required by `Error: eyre::kind::TraitKind` `Error: std::fmt::Display` which is required by `&Error: eyre::kind::AdhocKind` - `&Error: std::convert::Into>` - which is required by `&Error: eyre::kind::TraitKind<_>` + `&Error: std::convert::Into` + which is required by `&Error: eyre::kind::TraitKind` = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)