Why isn't anyhow implemented using Box<dyn std::error::Error>?
Sat, Dec 13 2025
The anyhow crate is a popular pick for handling errors in Rust applications.
The protagonist of the crate is the anyhow::Error type, which can wrap anything that implements std::error::Error
If I were to implement a catch-all error type to wrap any other error type, I would probably define it like this:
struct Error {
inner: Box<dyn std::error::Error>
}
impl<E: std::error::Error + 'static> From<E> for Error {
fn from(value: E) -> Error {
Error { inner: Box::new(value) }
}
}
anyhow does it quite differently:
#[repr(transparent)]
pub struct Error {
inner: Own<ErrorImpl>,
}
#[repr(C)]
pub(crate) struct ErrorImpl<E = ()> {
vtable: &'static ErrorVTable,
backtrace: Option<Backtrace>,
_object: E,
}
It's written this way to save space.
My Error struct is 16 bytes long1 because a boxed trait object is represented as a "fat pointer".
The anyhow implementation is originally based on the fehler crate, which in turn is based on the failure crate.
Upon hitting 1.0, fehler's author wrote a blog post, which contained this illuminating nugget about a new feature flag:1
Assuming a 64-bit machine.
The Error type, which uses heap allocation and dynamic dispatch, is designed for cases in which errors are very infrequent. For this reason, it is valuable to avoid pessimizing the happy path by making the Result type overly large. By default, the Error type is the size of two pointers - one to some data in the heap, and one to a vtable. With the small-error feature turned on, the size is cut in half to one pointer.
This works by storing the vtable inline inside of the Error type’s heap representation, instead of storing it next to the heap allocated pointer. The interior of the Error type is then a dynamically sized type, but the pointer to it just a single pointer instead of a wide pointer like trait objects are.
So the grandfather of anyhow was implemented the same way I would do it2, and the vtable business is an optimization that unfortunately pessimizes the clarity of the code.2
In older versions of rust, you would write Box<Trait> to denote a boxed trait object.
That leaves that weird Own type:
#[repr(transparent)]
pub struct Own<T>
where
T: ?Sized,
{
pub ptr: NonNull<T>,
}
In fact, the file where it's defined contains two other types: Ref & Mut.
Since vtables involve playing around with raw pointers, these types were added to give back some measure of type safety when doing so.