use std::any::Any;
use std::error::Error as StdError;
use std::fmt;
use std::sync::Arc;
pub struct TypeErasedBox {
field: Box<dyn Any + Send + Sync>,
#[allow(clippy::type_complexity)]
debug: Arc<
dyn Fn(&Box<dyn Any + Send + Sync>, &mut fmt::Formatter<'_>) -> fmt::Result + Send + Sync,
>,
#[allow(clippy::type_complexity)]
clone: Option<Arc<dyn Fn(&Box<dyn Any + Send + Sync>) -> TypeErasedBox + Send + Sync>>,
}
#[cfg(feature = "test-util")]
impl TypeErasedBox {
pub fn doesnt_matter() -> Self {
Self::new("doesn't matter")
}
}
impl fmt::Debug for TypeErasedBox {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("TypeErasedBox[")?;
if self.clone.is_some() {
f.write_str("Clone")?;
} else {
f.write_str("!Clone")?;
}
f.write_str("]:")?;
(self.debug)(&self.field, f)
}
}
impl TypeErasedBox {
pub fn new<T: Send + Sync + fmt::Debug + 'static>(value: T) -> Self {
let debug = |value: &Box<dyn Any + Send + Sync>, f: &mut fmt::Formatter<'_>| {
fmt::Debug::fmt(value.downcast_ref::<T>().expect("type-checked"), f)
};
Self {
field: Box::new(value),
debug: Arc::new(debug),
clone: None,
}
}
pub fn new_with_clone<T: Send + Sync + Clone + fmt::Debug + 'static>(value: T) -> Self {
let debug = |value: &Box<dyn Any + Send + Sync>, f: &mut fmt::Formatter<'_>| {
fmt::Debug::fmt(value.downcast_ref::<T>().expect("type-checked"), f)
};
let clone = |value: &Box<dyn Any + Send + Sync>| {
TypeErasedBox::new_with_clone(value.downcast_ref::<T>().expect("typechecked").clone())
};
Self {
field: Box::new(value),
debug: Arc::new(debug),
clone: Some(Arc::new(clone)),
}
}
pub fn try_clone(&self) -> Option<Self> {
Some((self.clone.as_ref()?)(&self.field))
}
pub fn downcast<T: fmt::Debug + Send + Sync + 'static>(self) -> Result<Box<T>, Self> {
let TypeErasedBox {
field,
debug,
clone,
} = self;
field.downcast().map_err(|field| Self {
field,
debug,
clone,
})
}
pub fn downcast_ref<T: fmt::Debug + Send + Sync + 'static>(&self) -> Option<&T> {
self.field.downcast_ref()
}
pub fn downcast_mut<T: fmt::Debug + Send + Sync + 'static>(&mut self) -> Option<&mut T> {
self.field.downcast_mut()
}
}
impl From<TypeErasedError> for TypeErasedBox {
fn from(value: TypeErasedError) -> Self {
TypeErasedBox {
field: value.field,
debug: value.debug,
clone: None,
}
}
}
pub struct TypeErasedError {
field: Box<dyn Any + Send + Sync>,
#[allow(clippy::type_complexity)]
debug: Arc<
dyn Fn(&Box<dyn Any + Send + Sync>, &mut fmt::Formatter<'_>) -> fmt::Result + Send + Sync,
>,
#[allow(clippy::type_complexity)]
as_error: Box<dyn for<'a> Fn(&'a TypeErasedError) -> &'a (dyn StdError) + Send + Sync>,
}
impl fmt::Debug for TypeErasedError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("TypeErasedError:")?;
(self.debug)(&self.field, f)
}
}
impl fmt::Display for TypeErasedError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt((self.as_error)(self), f)
}
}
impl StdError for TypeErasedError {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
(self.as_error)(self).source()
}
}
impl TypeErasedError {
pub fn new<T: StdError + Send + Sync + fmt::Debug + 'static>(value: T) -> Self {
let debug = |value: &Box<dyn Any + Send + Sync>, f: &mut fmt::Formatter<'_>| {
fmt::Debug::fmt(value.downcast_ref::<T>().expect("typechecked"), f)
};
Self {
field: Box::new(value),
debug: Arc::new(debug),
as_error: Box::new(|value: &TypeErasedError| {
value.downcast_ref::<T>().expect("typechecked") as _
}),
}
}
pub fn downcast<T: StdError + fmt::Debug + Send + Sync + 'static>(
self,
) -> Result<Box<T>, Self> {
let TypeErasedError {
field,
debug,
as_error,
} = self;
field.downcast().map_err(|field| Self {
field,
debug,
as_error,
})
}
pub fn downcast_ref<T: StdError + fmt::Debug + Send + Sync + 'static>(&self) -> Option<&T> {
self.field.downcast_ref()
}
pub fn downcast_mut<T: StdError + fmt::Debug + Send + Sync + 'static>(
&mut self,
) -> Option<&mut T> {
self.field.downcast_mut()
}
#[cfg(feature = "test-util")]
pub fn doesnt_matter() -> Self {
#[derive(Debug)]
struct FakeError;
impl fmt::Display for FakeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "FakeError")
}
}
impl StdError for FakeError {}
Self::new(FakeError)
}
}
#[cfg(test)]
mod tests {
use super::{TypeErasedBox, TypeErasedError};
use std::fmt;
#[derive(Debug)]
struct Foo(#[allow(dead_code)] &'static str);
#[derive(Debug)]
struct Bar(#[allow(dead_code)] isize);
#[derive(Debug, Clone, PartialEq, Eq)]
struct TestErr {
inner: &'static str,
}
impl TestErr {
fn new(inner: &'static str) -> Self {
Self { inner }
}
}
impl fmt::Display for TestErr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Error: {}", self.inner)
}
}
impl std::error::Error for TestErr {}
#[test]
fn test_typed_erased_errors_can_be_downcast() {
let test_err = TestErr::new("something failed!");
let type_erased_test_err = TypeErasedError::new(test_err.clone());
let actual = type_erased_test_err
.downcast::<TestErr>()
.expect("type erased error can be downcast into original type");
assert_eq!(test_err, *actual);
}
#[test]
fn test_typed_erased_errors_can_be_unwrapped() {
let test_err = TestErr::new("something failed!");
let type_erased_test_err = TypeErasedError::new(test_err.clone());
let actual = *type_erased_test_err
.downcast::<TestErr>()
.expect("type erased error can be downcast into original type");
assert_eq!(test_err, actual);
}
#[test]
fn test_typed_cloneable_boxes() {
let expected_str = "I can be cloned";
let cloneable = TypeErasedBox::new_with_clone(expected_str.to_owned());
let cloned = cloneable.try_clone().unwrap();
let actual_str = cloned.downcast_ref::<String>().unwrap();
assert_eq!(expected_str, actual_str);
assert_ne!(format!("{expected_str:p}"), format! {"{actual_str:p}"});
}
}