-rw-r--r--Cargo.lock34
-rw-r--r--Cargo.toml11
-rw-r--r--src/build.rs29
-rw-r--r--src/lib.rs16
-rw-r--r--src/protocol.rs89
-rw-r--r--src/protocol/id.rs14
-rw-r--r--src/protocols.rs0
-rw-r--r--src/walk.rs27
-rw-r--r--src/walk/walkers.rs2
9 files changed, 179 insertions, 43 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 00f8264..1742ff9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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",
]
diff --git a/Cargo.toml b/Cargo.toml
index ca48ef2..508659d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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>;
}
diff --git a/src/lib.rs b/src/lib.rs
index 703db86..b56d371 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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::*;