//! Interface for interfaces.
//!
//! ## Design
//! The design of protocols is based on an idea found in the
//! [`gdbstub`](https://docs.rs/gdbstub/latest/gdbstub/target/ext/index.html) crate.
//! This idea is of so called inlinable dyn extension traits.
//! However, in the form given in `gdbstub` they can't be used for arbitrary interfaces.
//! The main trait still needs to know about all the possible protocols.
//! That is where this module comes in.
//!
//! This module implements a technique we name dynamic inlinable dyn extension traits (DIDETs).
//! DIDETs adds one more layer to IDETs. Instead of a trait that knows all the possible protocols,
//! we have a single trait [`Implementer`] that allows looking up an extension trait
//! using a type ID. This may seem like it defeats the purpose of IDETs, that being to
//! make them inlinable. However, it turns out LLVM (the optimizer) is able to see
//! through this style of runtime reflection. As such, we still gain the benefits of
//! IDETs but with more flexability.
//! Protocols can now be defined in *any* crate and used between arbitrary crates.
//!
//! A protocol is a special trait that can participate as a DIDET. The only thing needed
//! for a protocol is an associated trait object. Because we need to use the
//! [`TypeId`][core::any::TypeId] of a protocol to perform reflection, we can't just use
//! the trait object itself as the protocol type. Instead an uninhabited type is used
//! as a marker for the trait.
//!
//! We then "implement" a protocol for a type by using [`Implementation`]. This provides
//! a mapping from `T` to the protocol's trait object.
//! By itself, [`Implementation`] is not enough for DIDET. A type also needs to implement
//! [`Implementer`] which allows looking up a particular [`Implementation`] trait object
//! from a [`ProtocolId`].
//!
//! The implementation of DIDETs defined by this module allows [`Implementer`] to be object safe.
//! 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,
/// 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 form of the trait.
///
/// This should be of the form `dyn Trait<'ctx> + 'a` where `'a` sets the
/// required lifetime of the trait object.
///
/// 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>()
}
}
pub trait Implementer<'ctx> {
fn interface(&self, id: ProtocolId) -> Option<AnyObject<'_, 'ctx, Ref>>;
}
pub trait ImplementerMut<'ctx> {
fn interface(&mut self, id: ProtocolId) -> Option<AnyObject<'_, 'ctx, Mut>>;
}
/// Extension trait for getting the implementation of a protocol.
pub trait ImplementerExt<'ctx>: Implementer<'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>>
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>>
where
'ctx: 'a,
{
match self.interface(P::id()) {
Some(interface) => match interface.downcast::<P>() {
Ok(implementation) => Some(implementation),
Err(interface) => panic!(
"unexpected protocol implementation: `{:?}`, expected: `{:?}`",
interface.id(),
P::id()
),
},
None => None,
}
}
}
/// Implement [`Implementer`] and [`Implementation`] for a set of protocols.
#[doc(hidden)]
#[macro_export]
macro_rules! implementer {
{
impl[$ctx:lifetime $($generic:tt)*] $name:ty = [$($protocol:ty),* $(,)?];
} => {
impl<$ctx $($generic)*> $crate::protocol::Implementer<$ctx> for $name {
#[inline]
fn interface(
&mut self,
id: $crate::protocol::ProtocolId
) -> ::core::option::Option<$crate::protocol::AnyObject<'_, $ctx>> {
match id {
$(id if id == $crate::protocol::ProtocolId::of::<$protocol>()
=> Some($crate::protocol::AnyObject::new::<$protocol>(self)),)*
_ => None
}
}
}
};
}
#[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
}
}
}
#[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> {}
}
}