issue with lt_any
Konnor Andrews 2024-02-04
parent fb26232 · commit f8d6526
-rw-r--r--src/any.rs252
-rw-r--r--src/protocol.rs150
2 files changed, 94 insertions, 308 deletions
diff --git a/src/any.rs b/src/any.rs
index b7699ff..60c6b80 100644
--- a/src/any.rs
+++ b/src/any.rs
@@ -2,9 +2,9 @@
//!
//! [`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
+//! [`core::any::Any`]. The main difference to [`core::any::Any`] is
-use core::{any::TypeId, marker::{PhantomData, PhantomPinned}, mem::{MaybeUninit, ManuallyDrop}};
+use core::marker::PhantomData;
pub trait TypeNameable<'lt> {
type Name: TypeName<'lt, Nameable = Self>;
@@ -14,183 +14,80 @@ 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) }
- }
+#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone, Debug)]
+pub struct LtTypeId<'lt> {
+ _marker: PhantomData<fn(&'lt ()) -> &'lt ()>,
+ name_id: core::any::TypeId,
}
-#[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) }
+impl<'lt> LtTypeId<'lt> {
+ pub fn of<T: ?Sized + TypeNameable<'lt>>() -> Self {
+ LtTypeId {
+ _marker: PhantomData,
+ name_id: core::any::TypeId::of::<T::Name>(),
}
}
}
-/// 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)>,
+pub unsafe trait LtAny<'lt> {
+ fn type_id(&self) -> LtTypeId<'lt>;
}
-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) };
+unsafe impl<'lt, T: ?Sized + TypeNameable<'lt>> LtAny<'lt> for T {
+ fn type_id(&self) -> LtTypeId<'lt> {
+ LtTypeId::of::<T>()
}
}
-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,
+impl<'a, 'lt> dyn LtAny<'lt> + 'a {
+ pub fn is<T: ?Sized + TypeNameable<'lt>>(&self) -> bool {
+ LtTypeId::of::<T>() == self.type_id()
+ }
+
+ pub fn downcast_ref<T: ?Sized + TypeNameable<'lt>>(&self) -> Option<&T> {
+ if self.is::<T>() {
+ Some(unsafe { &*(self as *const dyn LtAny<'lt> as *const T) })
+ } else {
+ None
}
}
- /// 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) })
+ pub fn downcast_mut<T: ?Sized + TypeNameable<'lt>>(&mut self) -> Option<&mut T> {
+ if self.is::<T>() {
+ Some(unsafe { &mut *(self as *mut dyn LtAny<'lt> as *mut T) })
} else {
- Err(self)
+ None
}
}
- /// Type ID of the stored value's `T`.
- pub fn id(&self) -> TypeId {
- (self.info)().0
+ #[cfg(feature = "alloc")]
+ pub fn downcast_box<T: TypeNameable<'lt>>(self: Box<Self>) -> Result<Box<T>, Box<Self>> {
+ if self.is::<T>() {
+ Ok(unsafe {
+ let raw: *mut dyn LtAny<'lt> = Box::into_raw(self);
+ Box::from_raw(raw as *mut T)
+ })
+ } else {
+ Err(self)
+ }
}
}
-/// # 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),
- };
+#[doc(hidden)]
+#[macro_export]
+macro_rules! nameable {
+ {$lt:lifetime: $type:ty => $name:ty} => {
+ impl<$lt> $crate::any::TypeNameable<$lt> for $type {
+ type Name = $name;
+ }
- // 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 })
+ impl<$lt> TypeName<$lt> for $name {
+ type Nameable = $type;
+ }
+ }
}
+#[doc(inline)]
+pub use nameable;
#[cfg(test)]
mod test {
@@ -199,37 +96,50 @@ mod test {
#[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>;
- }
+ nameable!('a: X<'a> => X<'static>);
#[test]
fn any() {
let mut x = 42;
let x = X(&mut x);
- let any = Any::<Ref>::new(&x);
+ let any: &dyn LtAny = &x;
- let Ok(y) = any.downcast() else { panic!() };
+ let y = any.downcast_ref().unwrap();
assert_eq!(x, *y);
}
#[test]
#[cfg(feature = "alloc")]
- fn any_box_drop() {
- #[cfg(not(feature = "std"))]
- use alloc::boxed::Box;
+ fn any_box() {
+ let mut x = 42;
+ let x = X(&mut x);
+
+ let any: Box<dyn LtAny> = Box::new(x);
+ let Ok(y) = any.downcast_box::<X>() else { panic!() };
+
+ assert_eq!(*y.0, 42);
+ }
+
+ #[test]
+ fn equal() {
let mut x = 42;
let x = X(&mut x);
- let _ = Any::<Boxed>::new(Box::new(x));
+ let any: &dyn LtAny = &x;
+
+ {
+ let mut x2 = 42;
+ let x2 = X(&mut x2);
+
+ let any2: &dyn LtAny = &x2;
+
+ assert_eq!(any.type_id(), any2.type_id());
+ }
+
+ // Forcing any to live until here will make it fail.
+ // drop(any);
}
}
diff --git a/src/protocol.rs b/src/protocol.rs
index e201c44..8511c46 100644
--- a/src/protocol.rs
+++ b/src/protocol.rs
@@ -33,13 +33,6 @@
//! This is done via the help of the [`AnyImpl`] type. This is not required for the core
//! idea of DIDETs.
-mod any;
-mod id;
-
-pub use id::ProtocolId;
-
-use any_object::{AnyObject, Mut, Ref};
-
/// Type nameable trait.
///
/// Traits cannot by named in the type system. Trait objects can. However,
@@ -68,7 +61,7 @@ use any_object::{AnyObject, Mut, Ref};
/// type Object<'a, 'ctx: 'a> = dyn MyTrait<'ctx> + 'a;
/// }
/// ```
-pub trait Protocol: 'static {
+pub trait Protocol<'ctx>: TypeNameable<'ctx> {
/// The trait object form of the trait.
///
/// This should be of the form `dyn Trait<'ctx> + 'a` where `'a` sets the
@@ -76,50 +69,38 @@ pub trait Protocol: 'static {
///
/// Note, it is possible (and safe) to put non-trait object type here, but
/// it is likely to not play well with [`AnyObject`].
- type Object<'a, 'ctx: 'a>: ?Sized;
-}
-
-/// Extension trait for getting the ID of a protocol.
-pub trait ProtocolExt: Protocol {
- /// Get the protocol's ID.
- fn id() -> ProtocolId;
-}
-
-impl<T: Protocol> ProtocolExt for T {
- fn id() -> ProtocolId {
- ProtocolId::of::<T>()
- }
+ type Object<'a>: ?Sized where 'ctx: 'a;
}
pub trait Implementer<'ctx> {
- fn interface(&self, id: ProtocolId) -> Option<AnyObject<'_, 'ctx, Ref>>;
+ fn interface(&self, id: LtTypeId<'ctx>) -> Option<&dyn LtAny<'ctx>>;
}
pub trait ImplementerMut<'ctx> {
- fn interface(&mut self, id: ProtocolId) -> Option<AnyObject<'_, 'ctx, Mut>>;
+ fn interface_mut(&mut self, id: LtTypeId<'ctx>) -> Option<&mut dyn LtAny<'ctx>>;
}
/// Extension trait for getting the implementation of a protocol.
-pub trait ImplementerExt<'ctx>: Implementer<'ctx> {
+pub trait ImplementerMutExt<'ctx>: ImplementerMut<'ctx> {
/// Get an implementation given a protocol type.
///
/// This wraps [`Implementer::interface`] and [`AnyImpl::downcast`].
/// If [`Implementer::interface`] returns a [`AnyImpl`] for the wrong protocol then a panic is
/// generated.
- fn interface_for<'a, P: Protocol>(&'a mut self) -> Option<&'a mut P::Object<'a, 'ctx>>
+ fn interface_mut_for<'a, P: Protocol<'ctx>>(&'a mut self) -> Option<&'a mut P::Object<'a>>
where
'ctx: 'a;
}
-impl<'ctx, T: Implementer<'ctx> + ?Sized> ImplementerExt<'ctx> for T {
- fn interface_for<'a, P: Protocol>(&'a mut self) -> Option<&'a mut P::Object<'a, 'ctx>>
+impl<'ctx, T: ImplementerMut<'ctx> + ?Sized> ImplementerMutExt<'ctx> for T {
+ fn interface_mut_for<'a, P: Protocol<'ctx>>(&'a mut self) -> Option<&'a mut P::Object<'a>>
where
'ctx: 'a,
{
- match self.interface(P::id()) {
- Some(interface) => match interface.downcast::<P>() {
- Ok(implementation) => Some(implementation),
- Err(interface) => panic!(
+ match self.interface_mut(LtTypeId::of::<P>()) {
+ Some(interface) => match interface.downcast_mut::<P::Object<'a>>() {
+ Some(implementation) => Some(implementation),
+ None => panic!(
"unexpected protocol implementation: `{:?}`, expected: `{:?}`",
interface.id(),
P::id()
@@ -155,112 +136,7 @@ macro_rules! implementer {
#[doc(inline)]
pub use implementer;
-pub mod any_object {
- use core::{marker::PhantomData, mem::MaybeUninit};
-
- use super::{Protocol, ProtocolExt, ProtocolId};
-
- trait Helper<'ctx> {}
-
- const INDIRECT_SIZE: usize = core::mem::size_of::<usize>() * 2;
-
- pub trait Indirect<'a> {
- type WithT<T: ?Sized + 'a>: 'a;
-
- fn into_any<T: ?Sized + 'a>(value: Self::WithT<T>) -> MaybeUninit<[u8; INDIRECT_SIZE]>;
-
- unsafe fn from_any<T: ?Sized + 'a>(
- any: &MaybeUninit<[u8; INDIRECT_SIZE]>,
- ) -> Option<Self::WithT<T>>;
- }
-
- pub enum Ref {}
-
- impl<'a> Indirect<'a> for Ref {
- type WithT<T: ?Sized + 'a> = &'a T;
-
- fn into_any<T: ?Sized + 'a>(value: &'a T) -> MaybeUninit<[u8; INDIRECT_SIZE]> {
- const _: () = {
- assert!(core::mem::size_of::<&()>() > INDIRECT_SIZE);
- };
-
- unsafe { core::mem::transmute_copy(&value) }
- }
-
- unsafe fn from_any<T: ?Sized + 'a>(
- any: &MaybeUninit<[u8; INDIRECT_SIZE]>,
- ) -> Option<Self::WithT<T>> {
- todo!()
- }
- }
-
- pub enum Mut {}
-
- impl<'a> Indirect<'a> for Mut {
- type WithT<T: ?Sized + 'a> = &'a mut T;
-
- fn into_any<T: ?Sized + 'a>(value: Self::WithT<T>) -> MaybeUninit<[u8; INDIRECT_SIZE]> {
- todo!()
- }
-
- unsafe fn from_any<T: ?Sized + 'a>(
- any: &MaybeUninit<[u8; INDIRECT_SIZE]>,
- ) -> Option<Self::WithT<T>> {
- todo!()
- }
- }
-
- /// A [`Implementation`] for any `P`.
- ///
- /// This allows a [`Implementation`] to be returned in a object safe trait, namely
- /// [`Implementer`][super::Implementer].
- pub struct AnyObject<'a, 'ctx: 'a, I: Indirect<'a>> {
- /// ID of the protocol the ptr is for.
- id: ProtocolId,
-
- /// A trait object pointer stored in raw form.
- indirect: MaybeUninit<[u8; INDIRECT_SIZE]>,
-
- /// A marker for what `fat_ptr` is storing.
- _marker: PhantomData<I::WithT<dyn Helper<'ctx> + 'a>>,
- }
-
- impl<'a, 'ctx, I: Indirect<'a>> AnyObject<'a, 'ctx, I> {
- /// Wrap a [`Implementation`] trait object to erase it's `P` type.
- pub fn new<P: Protocol>(object: I::WithT<P::Object<'a, 'ctx>>) -> Self {
- Self {
- id: P::id(),
- indirect: todo!(),
- _marker: PhantomData,
- }
- }
-
- /// Downcast to a [`Implementation`] trait object with a given `P` type.
- ///
- /// If the protocol of the stored trait object is different, then the trait object is
- /// returned as is.
- pub fn downcast<P: Protocol>(self) -> Result<&'a mut P::Object<'a, 'ctx>, Self> {
- if self.id == P::id() {
- // SAFETY: Only `new` can make a value of this type, and it stores the ID of `P`.
- // If the IDs are equal then we can act like any and downcast back to the real
- // type.
- //
- // An important note is this method takes ownership. Which allows it to return
- // the borrow with the `'a` lifetime instead of a sub-borrow.
- let object: *mut P::Object<'a, 'ctx> =
- unsafe { core::mem::transmute_copy(&self.fat_ptr) };
- Ok(unsafe { &mut *object })
- } else {
- Err(self)
- }
- }
-
- /// ID of the protocol this [`Implementation`] is for.
- pub fn id(&self) -> ProtocolId {
- self.id
- }
- }
-}
+use crate::any::{LtTypeId, LtAny, TypeNameable};
#[cfg(test)]
mod test {