From d3baa40c9b94c53d1d0dd89da589801b80ece4c4 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sun, 13 Mar 2022 15:16:00 +0100 Subject: [PATCH] Add MultitraitObject implementation Signed-off-by: trivernis --- .gitignore | 2 + .idea/.gitignore | 8 ++ .idea/discord.xml | 7 ++ .idea/modules.xml | 8 ++ .idea/multi-trait-object.iml | 11 +++ .idea/vcs.xml | 6 ++ Cargo.toml | 9 ++ README.md | 47 ++++++++++ src/lib.rs | 166 +++++++++++++++++++++++++++++++++++ src/macros.rs | 61 +++++++++++++ src/tests.rs | 82 +++++++++++++++++ 11 files changed, 407 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/discord.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/multi-trait-object.iml create mode 100644 .idea/vcs.xml create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 src/lib.rs create mode 100644 src/macros.rs create mode 100644 src/tests.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/discord.xml b/.idea/discord.xml new file mode 100644 index 0000000..30bab2a --- /dev/null +++ b/.idea/discord.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..fc4b6c0 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/multi-trait-object.iml b/.idea/multi-trait-object.iml new file mode 100644 index 0000000..c254557 --- /dev/null +++ b/.idea/multi-trait-object.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..7674c4c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "multi-trait-object" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dev-dependencies] +dyn-clone = "1.0.4" \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..678a2de --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# Multitrait Object + +This crate provides a pointer type that allows casting into +all registered traits for a given type. +This is done by storing the pointer to the v-table for each +trait implementation on the type as well as the pointer to the +data. + +## Usage + +```rust +use multi_trait_object::prelude::*; +use std::fmt::Debug; + +#[derive(Debug)] +struct MyStruct { + a: u64, +} + +trait MyTrait {} +trait MyOtherTrait {} + +impl MyTrait for MyStruct{} +impl MyOtherTrait for MyStruct {} + +impl_trait_object!(MyStruct, dyn MyTrait, dyn MyOtherTrait, dyn Debug); + +fn main() { + let obj = MyStruct { + a: 5 + }; + + let mto = obj.into_multitrait(); + + { + let debug = mto.downcast_trait::().unwrap(); + println!("{:?}", debug); + let my_trait = mto.downcast_trait::().unwrap(); + } + + let trait_box: Box = mto.downcast_trait_boxed::().unwrap(); +} +``` + +## License + +Apache-2.0 \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..0db3cfa --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,166 @@ +#![doc=include_str!("../README.md")] +#[cfg(test)] +mod tests; + +pub(crate) mod macros; + +use std::any::{Any, TypeId}; +use std::collections::HashMap; + +#[doc(hidden)] +#[derive(Copy, Clone, Debug)] +#[repr(C)] +pub struct FatPointer { + pub data: *const (), + pub vptr: *const (), +} + +/// A container to store data with the associated type and trait objects +/// allowing for casting down to traits or the concrete type +/// ```rust +/// use multi_trait_object::prelude::*; +/// use std::fmt::{Debug, Display}; +/// +/// let mut mto = MultitraitObject::new(String::new()); +/// register_traits!(mto, String, dyn Debug, dyn Display); +/// +/// let debug = mto.downcast_trait::().unwrap(); +/// println!("{:?}", debug); +/// let display = mto.downcast_trait::().unwrap(); +/// println!("{}", display); +/// let string = mto.downcast::().unwrap(); +/// println!("{}", string); +/// ``` +#[derive(Debug)] +pub struct MultitraitObject { + pub data: *mut (), + pub original_typeid: TypeId, + pub any_vtable: *const (), + pub traits: HashMap, +} + +impl MultitraitObject { + pub fn new(value: T) -> Self { + let any_vtable = __fat_pointer!(T as dyn Any).vptr; + let data = Box::into_raw(Box::new(value)) as *mut (); + + let mut this = Self { + data, + any_vtable, + original_typeid: TypeId::of::(), + traits: Default::default(), + }; + this._register::(any_vtable); + + this + } + + /// Downcasts the object into a reference of the given trait + pub fn downcast_trait(&self) -> Option<&T1> { + if std::mem::size_of::<&T1>() != std::mem::size_of::() { + None + } else { + unsafe { + self._downcast_trait::() + } + } + } + + /// Downcasts the object into a mutable reference of the given trait + pub fn downcast_trait_mut(&mut self) -> Option<&mut T1> { + if std::mem::size_of::<&T1>() != std::mem::size_of::() { + None + } else { + unsafe { + self._downcast_trait_mut::() + } + } + } + + /// Downcasts the object to a boxed representation of the given trait + pub fn downcast_trait_boxed(mut self) -> Option> { + if std::mem::size_of::<&T1>() != std::mem::size_of::() { + None + } else { + unsafe { + self._downcast_boxed_trait::() + } + } + } + + /// Downcasts the object into a reference of the given type + pub fn downcast_ref(&self) -> Option<&T> { + let any = self.downcast_trait::().unwrap(); + any.downcast_ref::() + } + + /// Downcasts the object into a mutable reference of the given type + pub fn downcast_mut(&mut self) -> Option<&mut T> { + let any = self.downcast_trait_mut::().unwrap(); + any.downcast_mut::() + } + + /// Downcasts the object into the given concrete type + pub fn downcast(self) -> Option { + if TypeId::of::() == self.original_typeid { + unsafe { + let typed_ptr = std::mem::transmute::<_, *mut T>(self.data); + let boxed = Box::from_raw(typed_ptr); + + Some(*boxed) + } + } else { + None + } + } + + #[doc(hidden)] + pub fn _register(&mut self, vtable_ptr: *const ()) { + self.traits.insert(TypeId::of::(), vtable_ptr); + } + + #[doc(hidden)] + unsafe fn _downcast_trait(&self) -> Option<&T1> { + let vptr = *self.traits.get(&TypeId::of::())?; + let fat_pointer = FatPointer { data: self.data, vptr }; + let value = std::mem::transmute::<_, &&T1>(&fat_pointer); + + Some(*value) + } + + #[doc(hidden)] + unsafe fn _downcast_trait_mut(&mut self) -> Option<&mut T1> { + let vptr = *self.traits.get(&TypeId::of::())?; + let mut fat_pointer = FatPointer { data: self.data, vptr }; + let value = std::mem::transmute::<_, &mut &mut T1>(&mut fat_pointer); + + Some(*value) + } + + #[doc(hidden)] + unsafe fn _downcast_boxed_trait(&mut self) -> Option> { + let vptr = *self.traits.get(&TypeId::of::())?; + let fat_pointer = FatPointer { data: self.data, vptr }; + let value = std::mem::transmute::<_, *const *mut T1>(&fat_pointer); + let value = Box::from_raw(*value); + + Some(value) + } +} + +impl Drop for MultitraitObject { + fn drop(&mut self) { + unsafe { + let raw = Box::from_raw(self.data); + std::mem::drop(raw); + } + } +} + +pub trait IntoMultitrait { + fn into_multitrait(self) -> MultitraitObject; +} + +pub mod prelude { + pub use crate::*; +} \ No newline at end of file diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000..6a4935c --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,61 @@ +/// Implements the `IntoMultitrait` trait on the defined type. +/// ```rust +/// use multi_trait_object::prelude::*; +/// +/// struct MyStruct { +/// a: u64, +/// } +/// +/// trait MyTrait {} +/// trait MyOtherTrait {} +/// +/// impl MyTrait for MyStruct{} +/// impl MyOtherTrait for MyStruct {} +/// +/// impl_trait_object!(MyStruct, dyn MyTrait, dyn MyOtherTrait); +/// ``` +#[macro_export] +macro_rules! impl_trait_object { + ($obj:ty, $($trt:ty),*) => { + impl IntoMultitrait for $obj { + fn into_multitrait(self) -> MultitraitObject { + let mut mto = MultitraitObject::new(self); + $( + register_traits!(mto, $obj, $trt); + )* + + mto + } + } + } +} + +/// Registers multiple traits on a multitrait object +/// ```rust +/// use multi_trait_object::prelude::*; +/// use std::fmt::{Debug, Display}; +/// +/// let value = String::new(); +/// let mut mto = MultitraitObject::new(value); +/// register_traits!(mto, String, dyn Debug, dyn Display); +/// ``` +#[macro_export] +macro_rules! register_traits { + ($r:expr, $v:ty, $($t:ty), +) => { + $( + $r._register::<$t>(__fat_pointer!($v as $t).vptr); + )+ + } +} + +#[doc(hidden)] +#[macro_export] +macro_rules! __fat_pointer { + ($v:ty as $t:ty) => {{ + let x = ::std::ptr::null::<$v>() as *const $v as *const $t; + #[allow(unused_unsafe)] + unsafe { + std::mem::transmute::<_, FatPointer>(x) + } + }} +} diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..77a7eab --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,82 @@ +use dyn_clone::{clone_box, DynClone}; +use std::fmt::Debug; +use crate::prelude::*; + +#[derive(Clone, Debug)] +struct TestStruct { + a: u32, + test: String, +} + +impl Default for TestStruct { + fn default() -> Self { + Self { + a: 5, + test: String::from("Hello World"), + } + } +} + +trait ChangeStruct { + fn update(&mut self); +} + +impl ChangeStruct for TestStruct { + fn update(&mut self) { + self.a = 6; + } +} + +impl_trait_object!(TestStruct, dyn DynClone, dyn ChangeStruct, dyn Debug); + +#[test] +fn it_creates_fat_pointers() { + let debug_vtable1 = __fat_pointer!(TestStruct as dyn Debug).vptr; + let dclone_vtable1 = __fat_pointer!(TestStruct as dyn DynClone).vptr; + let debug_vtable2 = __fat_pointer!(TestStruct as dyn Debug).vptr; + assert_eq!(debug_vtable1, debug_vtable2); + let dclone_vtable2 = __fat_pointer!(TestStruct as dyn DynClone).vptr; + assert_eq!(dclone_vtable1, dclone_vtable2); +} + +#[test] +fn it_constructs() { + TestStruct::default().into_multitrait(); +} + +#[test] +fn it_downcasts_traits() { + let mto = TestStruct::default().into_multitrait(); + let debug = mto.downcast_trait::().unwrap(); + let _ = format!("{:?}", debug); + let obj = mto.downcast_trait::().unwrap(); + let _new_obj = clone_box(&*obj); +} + +#[test] +fn it_downcasts_trait_mutable() { + let mut mto = TestStruct::default().into_multitrait(); + let change_struct = mto.downcast_trait_mut::().unwrap(); + change_struct.update(); +} + +#[test] +fn it_downcasts_boxed_traits() { + let mto = TestStruct::default().into_multitrait(); + let boxed = mto.downcast_trait_boxed::().unwrap(); + let _ = format!("{:?}", boxed); +} + +#[test] +fn it_downcasts_to_original() { + let mut mto = TestStruct::default().into_multitrait(); + { + mto.downcast_ref::().unwrap(); + } + { + mto.downcast_mut::().unwrap(); + } + let result = mto.downcast::().unwrap(); + assert_eq!(result.a, 5); + assert_eq!(result.test, String::from("Hello World")); +} \ No newline at end of file