Skip to content
This repository has been archived by the owner on Aug 16, 2021. It is now read-only.

Commit

Permalink
Optimize representation of Error without backtrace
Browse files Browse the repository at this point in the history
This change modifies Error to not store empty backtraces.
This saves space and allows zero-sized errors without
backtraces to be stored without any allocation.
  • Loading branch information
cramertj committed Nov 5, 2017
1 parent 05ef7b8 commit 6f32767
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 44 deletions.
4 changes: 1 addition & 3 deletions src/backtrace/internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,7 @@ impl InternalBacktrace {
}
}

pub(super) fn none() -> InternalBacktrace {
InternalBacktrace { backtrace: None }
}
pub(super) const NONE: InternalBacktrace = InternalBacktrace { backtrace: None };

pub(super) fn as_backtrace(&self) -> Option<&Backtrace> {
let bt = match self.backtrace {
Expand Down
6 changes: 4 additions & 2 deletions src/backtrace/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,10 @@ with_std! {
Backtrace { internal: InternalBacktrace::new() }
}

pub(crate) fn none() -> Backtrace {
Backtrace { internal: InternalBacktrace::none() }
pub(crate) const NONE: Backtrace = Backtrace { internal: InternalBacktrace::NONE };

pub(crate) fn is_none(&self) -> bool {
self.internal.as_backtrace().is_none()
}
}

Expand Down
109 changes: 70 additions & 39 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
use core::fmt::{self, Display, Debug};

use core::mem;
use core::ptr;

use Fail;
use backtrace::Backtrace;
use context::Context;
Expand All @@ -19,23 +16,60 @@ use compat::Compat;
/// information, and can be downcast into the `Fail`ure that underlies it for
/// more detailed inspection.
pub struct Error {
pub(crate) inner: Box<Inner<Fail>>,
pub(crate) inner: Box<FailOrWithBacktrace>,
}

pub(crate) struct Inner<F: ?Sized + Fail> {
pub(crate) struct WithBacktrace<F: Fail> {
backtrace: Backtrace,
pub(crate) failure: F,
failure: F,
}

static NO_BACKTRACE: &Backtrace = &Backtrace::NONE;

// Unsafe because the `is_withbacktrace` method must be correct for memory safety.
pub(crate) unsafe trait FailOrWithBacktrace: Send + Sync + 'static {
fn fail_ref(&self) -> &Fail;
fn fail_mut(&mut self) -> &mut Fail;
fn backtrace(&self) -> &Backtrace;
fn is_withbacktrace(&self) -> bool;
}

unsafe impl<T: Fail> FailOrWithBacktrace for T {
fn fail_ref(&self) -> &Fail { self }
fn fail_mut(&mut self) -> &mut Fail { self }
fn backtrace(&self) -> &Backtrace {
Fail::backtrace(self).unwrap_or(NO_BACKTRACE)
}
fn is_withbacktrace(&self) -> bool { false }
}

unsafe impl<T: Fail> FailOrWithBacktrace for WithBacktrace<T> {
fn fail_ref(&self) -> &Fail { &self.failure }
fn fail_mut(&mut self) -> &mut Fail { &mut self.failure }
fn backtrace(&self) -> &Backtrace {
&self.backtrace
}
fn is_withbacktrace(&self) -> bool { true }
}

impl<F: Fail> From<F> for Error {
fn from(failure: F) -> Error {
let inner: Inner<F> = {
let backtrace = if failure.backtrace().is_none() {
Backtrace::new()
} else { Backtrace::none() };
Inner { failure, backtrace }
let inner = if failure.backtrace().is_some() {
Box::new(failure)
} else {
// Attempt to add a backtrace
let backtrace = Backtrace::new();
if backtrace.is_none() {
Box::new(failure) as Box<FailOrWithBacktrace>
} else {
Box::new(WithBacktrace {
backtrace,
failure,
})
}
};
Error { inner: Box::new(inner) }

Error { inner }
}
}

Expand All @@ -44,7 +78,7 @@ impl Error {
/// method on `Fail`, this does not return an Option. The Error type
/// always has an underlying `Fail`ure.
pub fn cause(&self) -> &Fail {
&self.inner.failure
self.inner.fail_ref()
}

/// Get a reference to the Backtrace for this Error.
Expand All @@ -53,7 +87,7 @@ impl Error {
/// be returned. Otherwise, the backtrace will have been constructed at
/// the point that failure was cast into the Error type.
pub fn backtrace(&self) -> &Backtrace {
self.inner.failure.backtrace().unwrap_or(&self.inner.backtrace)
self.inner.backtrace()
}

/// Provide context for this Error.
Expand Down Expand Up @@ -89,21 +123,19 @@ impl Error {
/// the case that the underlying error is of a different type, the
/// original Error is returned.
pub fn downcast<T: Fail>(self) -> Result<T, Error> {
let ret: Option<T> = self.downcast_ref().map(|fail| {
unsafe {
// drop the backtrace
let _ = ptr::read(&self.inner.backtrace as *const Backtrace);
// read out the fail type
ptr::read(fail as *const T)
}
});
match ret {
Some(ret) => {
// forget self (backtrace is dropped, failure is moved
mem::forget(self);
Ok(ret)
}
_ => Err(self)
if self.downcast_ref::<T>().is_some() {
Ok(unsafe {
let is_withbacktrace = self.inner.is_withbacktrace();
let ptr: *mut FailOrWithBacktrace = Box::into_raw(self.inner);

if is_withbacktrace {
Box::from_raw(ptr as *mut WithBacktrace<T>).failure
} else {
*Box::from_raw(ptr as *mut T)
}
})
} else {
Err(self)
}
}

Expand All @@ -112,33 +144,32 @@ impl Error {
///
/// If the underlying error is not of type `T`, this will return `None`.
pub fn downcast_ref<T: Fail>(&self) -> Option<&T> {
self.inner.failure.downcast_ref()
self.inner.fail_ref().downcast_ref::<T>()
}

/// Attempt to downcast this Error to a particular `Fail` type by
/// mutable reference.
///
/// If the underlying error is not of type `T`, this will return `None`.
pub fn downcast_mut<T: Fail>(&mut self) -> Option<&mut T> {
self.inner.failure.downcast_mut()
self.inner.fail_mut().downcast_mut::<T>()
}
}

impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt(&self.inner.failure, f)
}
}

impl Debug for Inner<Fail> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Error {{ failure: {:?} }}\n\n{:?}", &self.failure, &self.backtrace)
Display::fmt(&self.cause(), f)
}
}

impl Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", &self.inner)
write!(f, "Error {{ failure: {:?} }}\n\n", self.inner.fail_ref())?;
let backtrace = self.inner.backtrace();
if !backtrace.is_none() {
write!(f, "{:?}", backtrace)?;
}
Ok(())
}
}

Expand Down

0 comments on commit 6f32767

Please sign in to comment.