Diffstat (limited to 'src/any.rs')
-rw-r--r--src/any.rs235
1 files changed, 235 insertions, 0 deletions
diff --git a/src/any.rs b/src/any.rs
new file mode 100644
index 0000000..b7699ff
--- /dev/null
+++ b/src/any.rs
@@ -0,0 +1,235 @@
+//! Heapless type erasure.
+//!
+//! [`Any`] is generic over the type of indirection (`&T`, `&mut T`, `Box<T>`)
+//! and allows erasing the `T` of the indirection. This is similar to replacing the `T` with
+//! [`core::any::Any`]. The main difference to [`core::any::Any`] is
+
+use core::{any::TypeId, marker::{PhantomData, PhantomPinned}, mem::{MaybeUninit, ManuallyDrop}};
+
+pub trait TypeNameable<'lt> {
+ type Name: TypeName<'lt, Nameable = Self>;
+}
+
+pub trait TypeName<'lt>: 'static {
+ type Nameable: ?Sized + TypeNameable<'lt, Name = Self>;
+}
+
+const INDIRECT_SIZE: usize = core::mem::size_of::<usize>() * 2;
+
+#[derive(Clone, Copy)]
+#[repr(transparent)]
+pub struct RawIndirectAny(MaybeUninit<[u8; INDIRECT_SIZE]>);
+
+pub unsafe trait Indirect<'a> {
+ type ForT<T: ?Sized + 'a>: 'a;
+
+ fn into_any<T: ?Sized + 'a>(value: Self::ForT<T>) -> RawIndirectAny;
+
+ unsafe fn from_any<T: ?Sized + 'a>(any: RawIndirectAny) -> Self::ForT<T>;
+}
+
+trait Helper {}
+
+/// A `&T` indirection.
+pub enum Ref {}
+
+const _: () = assert!(core::mem::size_of::<&dyn Helper>() <= core::mem::size_of::<RawIndirectAny>());
+
+unsafe impl<'a> Indirect<'a> for Ref {
+ type ForT<T: ?Sized + 'a> = &'a T;
+
+ fn into_any<T: ?Sized + 'a>(value: &'a T) -> RawIndirectAny {
+ unsafe { transmute::<&'a T, RawIndirectAny>(value) }
+ }
+
+ unsafe fn from_any<T: ?Sized + 'a>(any: RawIndirectAny) -> &'a T {
+ unsafe { transmute::<RawIndirectAny, &'a T>(any) }
+ }
+}
+
+/// A `&mut T` indirection.
+pub enum Mut {}
+
+const _: () = assert!(core::mem::size_of::<&mut dyn Helper>() <= core::mem::size_of::<RawIndirectAny>());
+
+unsafe impl<'a> Indirect<'a> for Mut {
+ type ForT<T: ?Sized + 'a> = &'a mut T;
+
+ fn into_any<T: ?Sized + 'a>(value: &'a mut T) -> RawIndirectAny {
+ unsafe { transmute::<&'a mut T, RawIndirectAny>(value) }
+ }
+
+ unsafe fn from_any<T: ?Sized + 'a>(any: RawIndirectAny) -> &'a mut T {
+ unsafe { transmute::<RawIndirectAny, &'a mut T>(any) }
+ }
+}
+
+#[cfg(feature = "alloc")]
+pub use boxed::*;
+#[cfg(feature = "alloc")]
+mod boxed {
+ use super::*;
+
+ #[cfg(not(feature = "std"))]
+ use alloc::boxed::Box;
+
+ /// A `Box<T>` indirection.
+ pub enum Boxed {}
+
+ const _: () = assert!(core::mem::size_of::<Box<dyn Helper>>() <= core::mem::size_of::<RawIndirectAny>());
+
+ unsafe impl<'a> Indirect<'a> for Boxed {
+ type ForT<T: ?Sized + 'a> = Box<T>;
+
+ fn into_any<T: ?Sized + 'a>(value: Box<T>) -> RawIndirectAny {
+ unsafe { transmute::<Box<T>, RawIndirectAny>(value) }
+ }
+
+ unsafe fn from_any<T: ?Sized + 'a>(any: RawIndirectAny) -> Box<T> {
+ unsafe { transmute::<RawIndirectAny, Box<T>>(any) }
+ }
+ }
+}
+
+/// Container for (almost) any indirection.
+///
+/// An indirection is something like a `&T` that is a pointer.
+/// The `'a` lifetime is the lifetime of the indirection, and `'lt`
+/// is a lifetime the stored value's `T` can contain.
+///
+/// This type is designed to allow using a generic containing type with
+/// a trait object method. While the value is stored in the [`Any`] it cannot
+/// be accessed. The [`Any`] must be downcasted with [`Any::downcast()`] to access
+/// the value.
+#[must_use]
+pub struct Any<'a, 'lt: 'a, I: Indirect<'a>> {
+ /// The meta information about the value is stored as a function pointer
+ /// to reduce it's size to one pointer.
+ info: fn() -> (TypeId, unsafe fn(RawIndirectAny)),
+
+ /// The indirect pointer.
+ indirect: RawIndirectAny,
+
+ /// Invariant over `'lt` and holding a `I::ForT`.
+ _marker: PhantomData<(I::ForT<()>, *mut &'lt (), PhantomPinned)>,
+}
+
+impl<'a, 'lt, I: Indirect<'a>> Drop for Any<'a, 'lt, I> {
+ fn drop(&mut self) {
+ // We need to drop the stored value.
+
+ // Lookup drop function.
+ let (_, drop_fn) = (self.info)();
+
+ // SAFETY: self.indirect is never touched again.
+ // Additionally, we know that drop_fn is for this self.indirect because it was
+ // made by Self::new.
+ unsafe { drop_fn(self.indirect) };
+ }
+}
+
+impl<'a, 'lt, I: Indirect<'a>> Any<'a, 'lt, I> {
+ /// Wrap an indirection.
+ ///
+ /// The inner type `T` of the indirection is erased.
+ pub fn new<T: TypeNameable<'lt>>(indirect: I::ForT<T>) -> Self {
+ Self {
+ info: || (
+ TypeId::of::<T::Name>(),
+ |raw| {
+ // SAFETY: This is only called in the drop impl.
+ unsafe { drop(I::from_any::<T>(raw))
+ }}
+ ),
+ indirect: I::into_any(indirect),
+ _marker: PhantomData,
+ }
+ }
+
+ /// Downcast to an indirection with a given `T` type.
+ ///
+ /// If the type of the stored value is different, then `self` is
+ /// returned as is.
+ pub fn downcast<T: TypeNameable<'lt>>(self) -> Result<I::ForT<T>, Self> {
+ let (id, _) = (self.info)();
+
+ if id == TypeId::of::<T::Name>() {
+ Ok(unsafe { I::from_any(self.indirect) })
+ } else {
+ Err(self)
+ }
+ }
+
+ /// Type ID of the stored value's `T`.
+ pub fn id(&self) -> TypeId {
+ (self.info)().0
+ }
+}
+
+/// # Safety
+/// Same rules as [`core::mem::transmute()`].
+unsafe fn transmute<T, U>(value: T) -> U {
+ // Create union type that can store a `T` or a `U`.
+ // We can then use this to convert between them.
+ //
+ // The repr(C) layout forces no offset between `t` and `u` as talked about here
+ // https://rust-lang.github.io/unsafe-code-guidelines/layout/unions.html#c-compatible-layout-repr-c
+ #[repr(C)]
+ union Transmute<T, U> {
+ t: ManuallyDrop<T>,
+ u: ManuallyDrop<U>,
+ }
+
+ // Create the union in the `T` state.
+ let value = Transmute {
+ t: ManuallyDrop::new(value),
+ };
+
+ // Read from the union in the `U` state.
+ // SAFETY: This is safe because the caller has promised that `T` can be transmuted to `U`.
+ // The following reference link talks about repr(C) unions being used this way.
+ // https://doc.rust-lang.org/reference/items/unions.html#reading-and-writing-union-fields
+ ManuallyDrop::into_inner(unsafe { value.u })
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[derive(Debug, PartialEq)]
+ struct X<'a>(&'a mut i32);
+
+ enum Y {}
+
+ impl<'a> TypeNameable<'a> for X<'a> {
+ type Name = Y;
+ }
+
+ impl<'a> TypeName<'a> for Y {
+ type Nameable = X<'a>;
+ }
+
+ #[test]
+ fn any() {
+ let mut x = 42;
+ let x = X(&mut x);
+
+ let any = Any::<Ref>::new(&x);
+
+ let Ok(y) = any.downcast() else { panic!() };
+
+ assert_eq!(x, *y);
+ }
+
+ #[test]
+ #[cfg(feature = "alloc")]
+ fn any_box_drop() {
+ #[cfg(not(feature = "std"))]
+ use alloc::boxed::Box;
+
+ let mut x = 42;
+ let x = X(&mut x);
+
+ let _ = Any::<Boxed>::new(Box::new(x));
+ }
+}