use std::{
any::Any,
backtrace::{Backtrace, BacktraceStatus},
fmt::{self, Debug, Display},
ops::Deref,
sync::Arc,
};
pub type Result<T = (), E = Error> = ::core::result::Result<T, E>;
#[derive(Clone)]
pub struct Error(Arc<dyn ErrorObject>);
impl Error {
pub fn msg(message: impl Display + Debug + Send + Sync + 'static) -> Self {
Self::from(Message(message))
}
#[must_use]
pub fn from_boxed(error: Box<dyn std::error::Error + Send + Sync + 'static>) -> Self {
Self::from(Boxed(error))
}
#[cfg(feature = "anyhow")]
#[must_use]
pub fn from_anyhow(error: anyhow::Error) -> Self {
Self::from_boxed(error.into())
}
#[must_use]
pub fn context(self, context: impl Display + Send + Sync + 'static) -> Self {
Self::from(WithContext {
context: Box::new(context),
error: self,
})
}
#[inline]
#[must_use]
pub fn is<E>(&self) -> bool
where
E: std::error::Error + Send + Sync + 'static,
{
self.downcast_ref::<E>().is_some()
}
pub fn downcast<E>(self) -> Result<E, DowncastError>
where
E: std::error::Error + Send + Sync + 'static,
{
if !self.is::<E>() {
return Err(DowncastError::new(DowncastFailure::Mismatch, self));
}
self.unwrap::<E>()
}
pub fn downcast_cloned<E>(self) -> Result<E, Self>
where
E: std::error::Error + Send + Sync + Clone + 'static,
{
match self.downcast::<E>() {
Ok(error) => Ok(error),
Err(failed) => {
let error = failed.into_error();
match error.downcast_ref::<E>() {
Some(error) => Ok(error.clone()),
None => Err(error),
}
}
}
}
#[must_use]
pub fn downcast_ref<E>(&self) -> Option<&E>
where
E: std::error::Error + Send + Sync + 'static,
{
let error = self.0.error();
match error.downcast_ref::<E>() {
Some(error) => Some(error),
None => error
.downcast_ref::<WithContext>()
.and_then(|context| context.error.downcast_ref::<E>()),
}
}
pub fn downcast_mut<E>(&mut self) -> Result<&mut E, DowncastFailure>
where
E: std::error::Error + Send + Sync + 'static,
{
if !self.is::<E>() {
return Err(DowncastFailure::Mismatch);
}
self.unwrap_mut::<E>()
}
fn unwrap<E>(self) -> Result<E, DowncastError>
where
E: std::error::Error + Send + Sync + 'static,
{
let any = match self.0.into_any().downcast::<Object<E>>() {
Ok(object) => {
return match Arc::try_unwrap(object) {
Ok(object) => Ok(object.error),
Err(shared) => Err(DowncastError::new(DowncastFailure::Shared, Self(shared))),
};
}
Err(any) => any,
};
let Ok(object) = any.downcast::<Object<WithContext>>() else {
unreachable!("an error holding an `E` is an `Object<E>` or a context layer");
};
match Arc::try_unwrap(object) {
Ok(object) => {
let WithContext { context, error } = object.error;
error.unwrap::<E>().map_err(|failed| {
let error = Self(Arc::new(Object {
error: WithContext {
context,
error: failed.error,
},
backtrace: object.backtrace,
}));
DowncastError::new(failed.failure, error)
})
}
Err(shared) => Err(DowncastError::new(DowncastFailure::Shared, Self(shared))),
}
}
fn unwrap_mut<E>(&mut self) -> Result<&mut E, DowncastFailure>
where
E: std::error::Error + Send + Sync + 'static,
{
let error = Arc::get_mut(&mut self.0)
.ok_or(DowncastFailure::Shared)?
.error_mut();
if error.is::<E>() {
return Ok(error
.downcast_mut::<E>()
.expect("the stored error was checked to be an `E`"));
}
error
.downcast_mut::<WithContext>()
.expect("an error holding an `E` is an `E` or a context layer")
.error
.unwrap_mut::<E>()
}
#[inline]
#[must_use]
pub fn backtrace(&self) -> &Backtrace {
self.0.backtrace()
}
pub fn chain(&self) -> impl Iterator<Item = &(dyn std::error::Error + 'static)> {
let mut next: Option<&(dyn std::error::Error + 'static)> = Some(self.0.error());
std::iter::from_fn(move || {
let current = next?;
next = current.source();
Some(current)
})
}
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(self.0.error(), f)?;
if f.alternate() {
for cause in self.chain().skip(1) {
write!(f, ": {cause}")?;
}
}
Ok(())
}
}
impl Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
return Debug::fmt(self.0.error(), f);
}
write!(f, "{}", self.0.error())?;
let mut causes = self.chain().skip(1).peekable();
if causes.peek().is_some() {
write!(f, "\n\nCaused by:")?;
for cause in causes {
write!(f, "\n {cause}")?;
}
}
let backtrace = self.backtrace();
if backtrace.status() == BacktraceStatus::Captured {
write!(f, "\n\nStack backtrace:\n{backtrace}")?;
}
Ok(())
}
}
impl Deref for Error {
type Target = dyn std::error::Error + Send + Sync + 'static;
fn deref(&self) -> &Self::Target {
self.0.error()
}
}
impl<E> From<E> for Error
where
E: std::error::Error + Send + Sync + 'static,
{
fn from(error: E) -> Self {
Self(Arc::new(Object {
error,
backtrace: Backtrace::capture(),
}))
}
}
impl From<Error> for Box<dyn std::error::Error + Send + Sync + 'static> {
fn from(error: Error) -> Self {
Box::new(BoxedError(error))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DowncastFailure {
Mismatch,
Shared,
}
impl Display for DowncastFailure {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Mismatch => f.write_str("the error is not an instance of the requested type"),
Self::Shared => f.write_str("the error is still shared with a clone"),
}
}
}
impl std::error::Error for DowncastFailure {}
#[derive(Debug)]
pub struct DowncastError {
failure: DowncastFailure,
error: Error,
}
impl DowncastError {
fn new(failure: DowncastFailure, error: Error) -> Self {
Self { failure, error }
}
#[must_use]
pub fn failure(&self) -> DowncastFailure {
self.failure
}
#[must_use]
pub fn error(&self) -> &Error {
&self.error
}
#[must_use]
pub fn into_error(self) -> Error {
self.error
}
}
trait ErrorObject: Send + Sync + 'static {
fn error(&self) -> &(dyn std::error::Error + Send + Sync + 'static);
fn error_mut(&mut self) -> &mut (dyn std::error::Error + Send + Sync + 'static);
fn backtrace(&self) -> &Backtrace;
fn into_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync>;
}
struct Object<E> {
error: E,
backtrace: Backtrace,
}
impl<E> ErrorObject for Object<E>
where
E: std::error::Error + Send + Sync + 'static,
{
fn error(&self) -> &(dyn std::error::Error + Send + Sync + 'static) {
&self.error
}
fn error_mut(&mut self) -> &mut (dyn std::error::Error + Send + Sync + 'static) {
&mut self.error
}
fn backtrace(&self) -> &Backtrace {
&self.backtrace
}
fn into_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync> {
self
}
}
struct Message<M>(M);
impl<M: Display> Display for Message<M> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(&self.0, f)
}
}
impl<M: Debug> Debug for Message<M> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Debug::fmt(&self.0, f)
}
}
impl<M: Display + Debug> std::error::Error for Message<M> {}
struct WithContext {
context: Box<dyn Display + Send + Sync>,
error: Error,
}
impl Display for WithContext {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(&self.context, f)
}
}
impl Debug for WithContext {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("WithContext")
.field("context", &format_args!("{}", self.context))
.field("error", &self.error)
.finish()
}
}
impl std::error::Error for WithContext {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(self.error.0.error())
}
}
struct Boxed(Box<dyn std::error::Error + Send + Sync + 'static>);
impl Display for Boxed {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(&self.0, f)
}
}
impl Debug for Boxed {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Debug::fmt(&self.0, f)
}
}
impl std::error::Error for Boxed {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.0.source()
}
}
struct BoxedError(Error);
impl Display for BoxedError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(&self.0, f)
}
}
impl Debug for BoxedError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Debug::fmt(&self.0, f)
}
}
impl std::error::Error for BoxedError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.0.0.error().source()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, Clone)]
struct Failure(&'static str);
impl Display for Failure {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.0)
}
}
impl std::error::Error for Failure {}
#[derive(Debug)]
struct WithCause {
cause: Failure,
}
impl Display for WithCause {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("outer")
}
}
impl std::error::Error for WithCause {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.cause)
}
}
#[test]
fn thiserror_transparent_forwards_display_and_source() {
#[derive(Debug, thiserror::Error)]
enum AppError {
#[error(transparent)]
Topcoat(#[from] Error),
}
let cause = Failure("boom");
let expected_cause = cause.to_string();
let error = Error::from(WithCause { cause });
let expected_message = error.to_string();
let app_error = AppError::from(error);
assert_eq!(app_error.to_string(), expected_message);
let source = std::error::Error::source(&app_error).unwrap();
assert!(source.is::<Failure>());
assert_eq!(source.to_string(), expected_cause);
assert!(source.source().is_none());
let app_error = AppError::from(Error::from(Failure("leaf")));
assert!(std::error::Error::source(&app_error).is_none());
}
#[test]
fn thiserror_from_preserves_the_stored_error_as_source() {
#[derive(Debug, thiserror::Error)]
enum AppError {
#[error("operation failed")]
Topcoat(#[from] Error),
}
let cause = Failure("boom");
let expected_cause = cause.to_string();
let error = Error::from(WithCause { cause });
let expected_message = error.to_string();
let app_error = AppError::from(error);
let source = std::error::Error::source(&app_error).unwrap();
assert!(source.is::<WithCause>());
assert_eq!(source.to_string(), expected_message);
let cause = source.source().unwrap();
assert!(cause.is::<Failure>());
assert_eq!(cause.to_string(), expected_cause);
assert!(cause.source().is_none());
}
#[test]
fn the_error_is_two_words_wide() {
assert_eq!(size_of::<Error>(), 2 * size_of::<usize>());
assert_eq!(size_of::<Result<()>>(), 2 * size_of::<usize>());
}
#[test]
fn clone_shares_the_underlying_error() {
let error = Error::from(Failure("boom"));
let clone = error.clone();
assert_eq!(error.to_string(), "boom");
assert_eq!(clone.to_string(), "boom");
assert_eq!(
clone.downcast_ref::<Failure>().map(|failure| failure.0),
Some("boom")
);
}
#[test]
fn msg_builds_an_error_from_a_message() {
let error = Error::msg(format!("boom {}", 1));
assert_eq!(error.to_string(), "boom 1");
assert!(error.is::<Message<String>>());
}
#[test]
fn downcast_extracts_a_unique_error() {
let failure = Error::from(Failure("boom")).downcast::<Failure>().unwrap();
assert_eq!(failure.0, "boom");
}
#[test]
fn downcast_reports_a_mismatch() {
let error = Error::from(std::io::Error::other("boom"));
let failed = error.downcast::<Failure>().unwrap_err();
assert_eq!(failed.failure(), DowncastFailure::Mismatch);
assert_eq!(failed.into_error().to_string(), "boom");
}
#[test]
fn downcast_reports_a_shared_error() {
let error = Error::from(Failure("boom"));
let clone = error.clone();
let failed = error.downcast::<Failure>().unwrap_err();
assert_eq!(failed.failure(), DowncastFailure::Shared);
let error = failed.into_error();
assert_eq!(error.to_string(), "boom");
drop(clone);
let failure = error.downcast::<Failure>().unwrap();
assert_eq!(failure.0, "boom");
}
#[test]
fn downcast_moves_out_of_a_context_layer() {
let error = Error::from(Failure("boom")).context("loading");
let failure = error.downcast::<Failure>().unwrap();
assert_eq!(failure.0, "boom");
}
#[test]
fn downcast_hands_a_context_layer_back_intact() {
let inner = Error::from(Failure("boom"));
let shared = inner.clone();
let error = inner.context("loading");
let failed = error.downcast::<Failure>().unwrap_err();
assert_eq!(failed.failure(), DowncastFailure::Shared);
let error = failed.into_error();
assert_eq!(format!("{error:#}"), "loading: boom");
drop(shared);
}
#[test]
fn downcast_cloned_extracts_a_unique_error() {
let failure = Error::from(Failure("boom"))
.downcast_cloned::<Failure>()
.unwrap();
assert_eq!(failure.0, "boom");
}
#[test]
fn downcast_cloned_clones_a_shared_error() {
let error = Error::from(Failure("boom"));
let clone = error.clone();
let failure = error.downcast_cloned::<Failure>().unwrap();
assert_eq!(failure.0, "boom");
assert_eq!(clone.to_string(), "boom");
}
#[test]
fn downcast_cloned_keeps_a_non_matching_error() {
let error = Error::from(std::io::Error::other("boom"));
let clone = error.clone();
let error = error.downcast_cloned::<Failure>().unwrap_err();
assert_eq!(error.to_string(), "boom");
drop(clone);
}
#[test]
fn downcast_mut_mutates_a_unique_error() {
let mut error = Error::from(Failure("boom"));
error.downcast_mut::<Failure>().unwrap().0 = "bang";
assert_eq!(error.to_string(), "bang");
}
#[test]
fn downcast_mut_mutates_through_a_context_layer() {
let mut error = Error::from(Failure("boom")).context("loading");
error.downcast_mut::<Failure>().unwrap().0 = "bang";
assert_eq!(format!("{error:#}"), "loading: bang");
}
#[test]
fn downcast_mut_reports_a_mismatch() {
let mut error = Error::from(std::io::Error::other("boom"));
assert_eq!(
error.downcast_mut::<Failure>().unwrap_err(),
DowncastFailure::Mismatch
);
}
#[test]
fn downcast_mut_reports_a_shared_error() {
let mut error = Error::from(Failure("boom"));
let clone = error.clone();
assert_eq!(
error.downcast_mut::<Failure>().unwrap_err(),
DowncastFailure::Shared
);
drop(clone);
assert!(error.downcast_mut::<Failure>().is_ok());
}
#[test]
fn context_wraps_the_message_and_keeps_the_error_reachable() {
let error = Error::from(Failure("boom")).context("loading");
assert_eq!(error.to_string(), "loading");
assert_eq!(format!("{error:#}"), "loading: boom");
assert_eq!(error.chain().count(), 2);
assert!(error.is::<Failure>());
assert_eq!(error.downcast_cloned::<Failure>().unwrap().0, "boom");
}
#[test]
fn display_and_debug_walk_the_chain() {
let error = Error::from(WithCause {
cause: Failure("boom"),
});
assert_eq!(error.to_string(), "outer");
assert_eq!(format!("{error:#}"), "outer: boom");
assert!(format!("{error:?}").starts_with("outer\n\nCaused by:\n boom"));
assert!(format!("{error:#?}").starts_with("WithCause"));
}
#[test]
fn box_conversion_keeps_the_message_and_source() {
let error = Error::from(WithCause {
cause: Failure("boom"),
});
let shared = error.clone();
let boxed: Box<dyn std::error::Error + Send + Sync> = error.into();
assert_eq!(boxed.to_string(), "outer");
assert_eq!(
boxed.source().map(ToString::to_string),
Some(String::from("boom"))
);
drop(shared);
}
#[test]
fn boxed_errors_convert_with_their_chain() {
let boxed: Box<dyn std::error::Error + Send + Sync> = Box::new(WithCause {
cause: Failure("boom"),
});
let error = Error::from_boxed(boxed);
assert_eq!(error.to_string(), "outer");
assert_eq!(format!("{error:#}"), "outer: boom");
}
#[cfg(feature = "anyhow")]
#[test]
fn anyhow_errors_convert_with_their_chain() {
let error = Error::from_anyhow(anyhow::anyhow!("boom").context("loading"));
assert_eq!(error.to_string(), "loading");
assert_eq!(format!("{error:#}"), "loading: boom");
}
}