Diffstat (limited to 'src/protocol.rs')
-rw-r--r--src/protocol.rs205
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> {}
+ }
+}