added some docs
| -rw-r--r-- | Cargo.lock | 34 | ||||
| -rw-r--r-- | Cargo.toml | 11 | ||||
| -rw-r--r-- | src/build.rs | 29 | ||||
| -rw-r--r-- | src/lib.rs | 16 | ||||
| -rw-r--r-- | src/protocol.rs | 89 | ||||
| -rw-r--r-- | src/protocol/id.rs | 14 | ||||
| -rw-r--r-- | src/protocols.rs | 0 | ||||
| -rw-r--r-- | src/walk.rs | 27 | ||||
| -rw-r--r-- | src/walk/walkers.rs | 2 |
9 files changed, 179 insertions, 43 deletions
@@ -3,41 +3,41 @@ version = 3 [[package]] -name = "no-std-thiserror" -version = "0.1.0" +name = "proc-macro2" +version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd51a908f34bf466b13228b1f47639bf281c5580f6ef96a70cd6ddde90ce0380" +checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" dependencies = [ - "no-std-thiserror-impl", + "unicode-ident", ] [[package]] -name = "no-std-thiserror-impl" -version = "1.0.56" +name = "quote" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0db3f7230e5876bd71547b860ac196513a1e38f0a854eea0cca293ccf055f554" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", - "quote", - "syn", ] [[package]] -name = "proc-macro2" -version = "1.0.76" +name = "serde" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" +checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" dependencies = [ - "unicode-ident", + "serde_derive", ] [[package]] -name = "quote" -version = "1.0.35" +name = "serde_derive" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" dependencies = [ "proc-macro2", + "quote", + "syn", ] [[package]] @@ -61,5 +61,5 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" name = "uniserde" version = "0.1.0" dependencies = [ - "no-std-thiserror", + "serde", ] @@ -3,12 +3,11 @@ name = "uniserde" version = "0.1.0" edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] -no-std-thiserror = { version = "0.1.0", default-features = false } +serde = { version = "1.0", default-features = false, optional = true } [features] -default = ["alloc"] -std = ["alloc", "no-std-thiserror/std"] -alloc = [] +default = [] +std = ["alloc", "serde?/std"] +alloc = ["serde?/alloc"] +serde = ["dep:serde"] diff --git a/src/build.rs b/src/build.rs index b0b5750..0322c6a 100644 --- a/src/build.rs +++ b/src/build.rs @@ -1,24 +1,41 @@ -pub mod builders; -pub mod protocols; +// pub mod builders; +// pub mod protocols; use crate::protocol::Implementer; -/// A type buildable from a walker. +/// A buildable type. pub trait Build<'ctx>: Sized { - /// The builder that can be used to build a value. + /// The builder that can be used to build a value of `Self`. type Builder: Builder<'ctx, Value = Self>; } -/// Extension to [`Visitor`] that allows constructing and finishing the visitor. +/// Builder for a type. +/// +/// The `'ctx` lifetime is some lifetime that is longer than the walker. +/// As such, the built value can borrow from other data with a `'ctx` lifetimes. +/// +/// A builder allows creating a value of a type [`Self::Value`]. +/// The way to use a builder is as follows. +/// - Call [`Default::default()`] to create an instance of the builder. +/// - Call [`Self::as_visitor()`] and give it to a walker's [`walk()`][crate::walk::Walker::walk]. The walker will then fill +/// the builder with data from it's walk. +/// - Call [`Self::build()`] to finish building the value and get any errors +/// that happened during filling it with data. pub trait Builder<'ctx>: Default { + /// Error that can happen during filling the builder with data. type Error; /// Type to be built. type Value; - /// As a visitor. + /// Get the builder as a visitor that a walker can use. + /// + /// This is expected to just be `self`. fn as_visitor(&mut self) -> &mut dyn Implementer<'ctx>; /// Finish the value. + /// + /// If an error happened with the builder during the walk + /// it will be reported here. fn build(self) -> Result<Self::Value, Self::Error>; } @@ -6,15 +6,15 @@ #[cfg(feature = "alloc")] extern crate alloc; -pub mod build; -pub mod impls; pub mod protocol; -// pub mod protocols; -pub mod transform; +pub mod build; pub mod walk; -pub use build::Build; -pub use build::Builder; +// pub mod impls; +// pub mod transform; -pub use walk::Walk; -pub use walk::Walker; +// pub use build::Build; +// pub use build::Builder; +// +// pub use walk::Walk; +// pub use walk::Walker; diff --git a/src/protocol.rs b/src/protocol.rs index 124d270..acd22b8 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -1,13 +1,53 @@ +//! # 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 id; pub use any_implementation::AnyImpl; pub use id::ProtocolId; +/// An interface for interfaces. pub trait Protocol: 'static { + /// The trait object for the interface. + /// + /// The trait this object is of, is the actual interface. + /// + /// Note, this types is not required to be a trait object, but it is expected. type Object<'a, 'ctx: 'a>; } +/// Extension trait for getting the ID of a protocol. pub trait ProtocolExt: Protocol { + /// Get the protocol's ID. fn id() -> ProtocolId; } @@ -17,15 +57,41 @@ 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>>; } +/// 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>; } +/// 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<P: Protocol>(&mut self) -> Option<&mut dyn Implementation<'ctx, P>>; } @@ -35,7 +101,7 @@ impl<'ctx, T: Implementer<'ctx> + ?Sized> ImplementerExt<'ctx> for T { Some(interface) => match interface.downcast::<P>() { Ok(implementation) => Some(implementation), Err(interface) => panic!( - "unexpected type ID for protocol implementation: `{:?}`, expected: `{:?}`", + "unexpected protocol implementation: `{:?}`, expected: `{:?}`", interface.id(), P::id() ), @@ -45,6 +111,7 @@ impl<'ctx, T: Implementer<'ctx> + ?Sized> ImplementerExt<'ctx> for T { } } +/// Implement [`Implementer`] and [`Implementation`] for a set of protocols. #[doc(hidden)] #[macro_export] macro_rules! implementer { @@ -90,17 +157,30 @@ mod any_implementation { use super::{Implementation, Protocol, ProtocolExt, ProtocolId}; + /// Helper trait to make sure AnyImpl has the correct properties. trait ErasedImplementation<'ctx> {} + /// 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>>(); + /// 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> { + /// 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]>, + + /// A marker for what `fat_ptr` is storing. _marker: PhantomData<&'a mut dyn ErasedImplementation<'ctx>>, } impl<'a, 'ctx> AnyImpl<'a, 'ctx> { + /// Wrap a [`Implementation`] trait object to erase it's `P` type. pub fn new<P: Protocol>(implementation: &'a mut dyn Implementation<'ctx, P>) -> Self { Self { id: P::id(), @@ -111,6 +191,10 @@ mod any_implementation { } } + /// 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 dyn Implementation<'ctx, P>, Self> { if self.id == P::id() { // SAFETY: Only `new` can make a value of this type, and it stores the ID of `P`. @@ -118,13 +202,14 @@ mod any_implementation { // type. // // An important note is this method takes ownership. Which allows it to return - // the borrow with the `'walking` lifetime instead of a sub-borrow. + // the borrow with the `'a` lifetime instead of a sub-borrow. Ok(unsafe { core::mem::transmute(self.fat_ptr) }) } else { Err(self) } } + /// ID of the protocol this [`Implementation`] is for. pub fn id(&self) -> ProtocolId { self.id } diff --git a/src/protocol/id.rs b/src/protocol/id.rs index f1a1723..40aa2bc 100644 --- a/src/protocol/id.rs +++ b/src/protocol/id.rs @@ -2,13 +2,20 @@ use core::any::TypeId; use super::Protocol; +/// ID of a protocol. +/// +/// An ID also includes the protocol's type name for easier debugging. #[derive(Copy, Clone)] pub struct ProtocolId { + /// Type ID of the protocol type. id: fn() -> TypeId, + + /// Name of the protocol type. name: fn() -> &'static str, } impl ProtocolId { + /// Get the ID of a protocol. pub const fn of<P: Protocol>() -> Self { Self { id: || core::any::TypeId::of::<P>(), @@ -16,10 +23,16 @@ impl ProtocolId { } } + /// Type ID of the protocol. + /// + /// This is used for comparision. fn id(&self) -> TypeId { (self.id)() } + /// Type name of the protocol. + /// + /// This is used for debugging purposes. fn name(&self) -> &'static str { (self.name)() } @@ -36,6 +49,7 @@ impl core::fmt::Debug for ProtocolId { impl core::fmt::Display for ProtocolId { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + // Just print the type name. self.name().fmt(f) } } diff --git a/src/protocols.rs b/src/protocols.rs deleted file mode 100644 index e69de29..0000000 --- a/src/protocols.rs +++ /dev/null diff --git a/src/walk.rs b/src/walk.rs index 8f3907e..b24a9a3 100644 --- a/src/walk.rs +++ b/src/walk.rs @@ -1,15 +1,34 @@ -pub mod protocols; -pub mod walkers; +// pub mod protocols; +// pub mod walkers; use crate::protocol::Implementer; +/// A type that can be walked. pub trait Walk<'ctx>: Sized { + /// The walker for the type. type Walker: Walker<'ctx> + From<Self>; } +/// Walker for a type. +/// +/// The `'ctx` lifetime is some lifetime that is longer than `Self`. +/// Data from the value may borrow using `'ctx`. +/// +/// The way to use a walker is as follows. +/// - Call [From::from()] with a value to be walked to make a walker. +/// - Call [Self::walk()] to walk the value. Data will be sent to the provided +/// visitor. pub trait Walker<'ctx> { + /// Error that can happen while walking the value. type Error; - type Value; - fn walk(self, visitor: &mut dyn Implementer<'ctx>) -> Result<Self::Value, Self::Error>; + /// An arbitrary type the walker is left with after walking. + /// + /// Its recommended that this is `Self` if the walker is repeatable. + type Output; + + /// Walk the value. + /// + /// The walker should send data to the `visitor` as it walks the value. + fn walk(self, visitor: &mut dyn Implementer<'ctx>) -> Result<Self::Output, Self::Error>; } diff --git a/src/walk/walkers.rs b/src/walk/walkers.rs index b4dd2cd..fdab12e 100644 --- a/src/walk/walkers.rs +++ b/src/walk/walkers.rs @@ -3,6 +3,8 @@ use crate::protocol::ProtocolId; mod owned; mod owned_clone; +mod serde; + pub use owned::*; pub use owned_clone::*; |