Diffstat (limited to 'src/protocol.rs')
| -rw-r--r-- | src/protocol.rs | 205 |
1 files changed, 135 insertions, 70 deletions
diff --git a/src/protocol.rs b/src/protocol.rs index 17bd651..e201c44 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -33,19 +33,50 @@ //! 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 any_implementation::AnyImpl; pub use id::ProtocolId; -/// An interface for interfaces. +use any_object::{AnyObject, Mut, Ref}; + +/// Type nameable trait. +/// +/// Traits cannot by named in the type system. Trait objects can. However, +/// trait objects are not always `'static` so don't always have a +/// [`TypeId`][core::any::TypeId]. This trait provides a way to name a trait object +/// in the type system and with a `TypeId` even if it contains lifetimes. +/// +/// [`Protocol`] should be implemented on a marker type that is `'static`. +/// [`Protocol`] then gives a mapping from that marker type to a trait object +/// given by [`Protocol::Object`]. Its recommended to use uninhabited marker +/// types when possible as the marker never needs to exist as a value. +/// +/// The `'ctx` lifetime is a lifetime the trait object can contain. The `'a` lifetime +/// is the lifetime of a reference to the trait object. As such, the trait object +/// needs to live for at least `'a`. +/// +/// ``` +/// // Some trait we want to use as a protocol. +/// trait MyTrait<'ctx> {} +/// +/// // Type to name MyTrait in the type system. +/// enum MyTraitProtocol {} +/// +/// // By implementing this we map MyTraitProtocol to MyTrait. +/// impl Protocol for MyTraitProtocol { +/// type Object<'a, 'ctx: 'a> = dyn MyTrait<'ctx> + 'a; +/// } +/// ``` pub trait Protocol: 'static { - /// The trait object for the interface. + /// The trait object form of the trait. /// - /// The trait this object is of, is the actual interface. + /// This should be of the form `dyn Trait<'ctx> + 'a` where `'a` sets the + /// required lifetime of the trait object. /// - /// Note, this types is not required to be a trait object, but it is expected. - type Object<'a, 'ctx: 'a>; + /// 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. @@ -60,32 +91,12 @@ impl<T: Protocol> ProtocolExt for T { } } -/// An implementer of zero, one or more protocols. -/// -/// Types that implement this trait have a form of reflection over the traits they implement. -/// The only traits accessible using this are ones that are described by a protocol. pub trait Implementer<'ctx> { - /// Lookup the interface for a given protocol. - /// - /// The returned implementation is expected to just be `self` but as a - /// `&mut dyn Implementation<'ctx, P>`. This is not required though. - /// - /// The returned [`AnyImpl`] could be for a different protocol; This is considered - /// a bug in an implementation and can be resolved via a panic. This is how - /// [`ImplementerExt::interface_for`] behaves. - /// - /// If `self` doesn't implement the given protocol, then a `None` is returned. - fn interface(&mut self, id: ProtocolId) -> Option<AnyImpl<'_, 'ctx>>; + fn interface(&self, id: ProtocolId) -> Option<AnyObject<'_, 'ctx, Ref>>; } -/// An implementation of a protocol. -/// -/// This is a formalization of `self as &mut dyn Trait`. -pub trait Implementation<'ctx, P: Protocol> { - /// Convert to the trait object for the protocol. - /// - /// Its expected that the returned value is just `self` acting as a trait object. - fn as_object(&mut self) -> P::Object<'_, 'ctx>; +pub trait ImplementerMut<'ctx> { + fn interface(&mut self, id: ProtocolId) -> Option<AnyObject<'_, 'ctx, Mut>>; } /// Extension trait for getting the implementation of a protocol. @@ -95,11 +106,16 @@ pub trait ImplementerExt<'ctx>: Implementer<'ctx> { /// 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<P: Protocol>(&mut self) -> Option<&mut dyn Implementation<'ctx, P>>; + fn interface_for<'a, P: Protocol>(&'a mut self) -> Option<&'a mut P::Object<'a, 'ctx>> + where + 'ctx: 'a; } impl<'ctx, T: Implementer<'ctx> + ?Sized> ImplementerExt<'ctx> for T { - fn interface_for<P: Protocol>(&mut self) -> Option<&mut dyn Implementation<'ctx, P>> { + fn interface_for<'a, P: Protocol>(&'a mut self) -> Option<&'a mut P::Object<'a, 'ctx>> + where + 'ctx: 'a, + { match self.interface(P::id()) { Some(interface) => match interface.downcast::<P>() { Ok(implementation) => Some(implementation), @@ -126,74 +142,95 @@ macro_rules! implementer { fn interface( &mut self, id: $crate::protocol::ProtocolId - ) -> ::core::option::Option<$crate::protocol::AnyImpl<'_, $ctx>> { + ) -> ::core::option::Option<$crate::protocol::AnyObject<'_, $ctx>> { match id { $(id if id == $crate::protocol::ProtocolId::of::<$protocol>() - => Some($crate::protocol::AnyImpl::new::<$protocol>(self)),)* + => Some($crate::protocol::AnyObject::new::<$protocol>(self)),)* _ => None } } } - - $crate::implementer! { - $ctx $name [$($protocol),*] [$($generic)*] - } - }; - { - $ctx:lifetime $name:ty [$protocol:ty] [$($generic:tt)*] - } => { - impl<$ctx $($generic)*> $crate::protocol::Implementation<$ctx, $protocol> for $name { - fn as_object(&mut self) -> <$protocol as $crate::protocol::Protocol>::Object<'_, $ctx> { - self - } - } - }; - { - $ctx:lifetime $name:ty [$($protocol:ty),*] $generic:tt - } => { - $($crate::implementer! { - $ctx $name [$protocol] $generic - })* }; } #[doc(inline)] pub use implementer; -mod any_implementation { +pub mod any_object { use core::{marker::PhantomData, mem::MaybeUninit}; - use super::{Implementation, Protocol, ProtocolExt, ProtocolId}; + 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; - /// Helper trait to make sure AnyImpl has the correct properties. - trait ErasedImplementation<'ctx> {} + fn into_any<T: ?Sized + 'a>(value: Self::WithT<T>) -> MaybeUninit<[u8; INDIRECT_SIZE]>; - /// Size of a trait object. - /// This should always be 2 pointers in size. - const DYN_PTR_SIZE: usize = core::mem::size_of::<&mut dyn ErasedImplementation<'static>>(); + 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 AnyImpl<'a, 'ctx> { + 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. - fat_ptr: MaybeUninit<[u8; DYN_PTR_SIZE]>, + indirect: MaybeUninit<[u8; INDIRECT_SIZE]>, /// A marker for what `fat_ptr` is storing. - _marker: PhantomData<&'a mut dyn ErasedImplementation<'ctx>>, + _marker: PhantomData<I::WithT<dyn Helper<'ctx> + 'a>>, } - impl<'a, 'ctx> AnyImpl<'a, 'ctx> { + 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>(implementation: &'a mut dyn Implementation<'ctx, P>) -> Self { + pub fn new<P: Protocol>(object: I::WithT<P::Object<'a, 'ctx>>) -> Self { Self { id: P::id(), - // SAFETY: A maybe uninit array of bytes can hold any pointer. - // Additionally, transmute makes sure the size is correct. - fat_ptr: unsafe { core::mem::transmute(implementation) }, + indirect: todo!(), _marker: PhantomData, } } @@ -202,7 +239,7 @@ mod any_implementation { /// /// 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 dyn Implementation<'ctx, P>, Self> { + 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 @@ -210,7 +247,9 @@ mod any_implementation { // // An important note is this method takes ownership. Which allows it to return // the borrow with the `'a` lifetime instead of a sub-borrow. - Ok(unsafe { core::mem::transmute(self.fat_ptr) }) + let object: *mut P::Object<'a, 'ctx> = + unsafe { core::mem::transmute_copy(&self.fat_ptr) }; + Ok(unsafe { &mut *object }) } else { Err(self) } @@ -222,3 +261,29 @@ mod any_implementation { } } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn implementer_macro() { + struct X<T>(T); + + enum Y {} + + trait Z {} + + impl Protocol for Y { + type Object<'a, 'ctx> = dyn Z + 'a; + } + + implementer! { + impl['ctx, T: Clone] X<T> = [ + Y + ]; + } + + impl<T: Clone> Z for X<T> {} + } +} |