Add MultitraitObject implementation
Signed-off-by: trivernis <trivernis@protonmail.com>main
commit
d3baa40c9b
@ -0,0 +1,2 @@
|
|||||||
|
/target
|
||||||
|
Cargo.lock
|
@ -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
|
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DiscordProjectSettings">
|
||||||
|
<option name="show" value="ASK" />
|
||||||
|
<option name="description" value="" />
|
||||||
|
</component>
|
||||||
|
</project>
|
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/multi-trait-object.iml" filepath="$PROJECT_DIR$/.idea/multi-trait-object.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="CPP_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
@ -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"
|
@ -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::<dyn Debug>().unwrap();
|
||||||
|
println!("{:?}", debug);
|
||||||
|
let my_trait = mto.downcast_trait::<dyn MyTrait>().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let trait_box: Box<dyn MyTrait> = mto.downcast_trait_boxed::<dyn MyTrait>().unwrap();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Apache-2.0
|
@ -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::<dyn Debug>().unwrap();
|
||||||
|
/// println!("{:?}", debug);
|
||||||
|
/// let display = mto.downcast_trait::<dyn Display>().unwrap();
|
||||||
|
/// println!("{}", display);
|
||||||
|
/// let string = mto.downcast::<String>().unwrap();
|
||||||
|
/// println!("{}", string);
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct MultitraitObject {
|
||||||
|
pub data: *mut (),
|
||||||
|
pub original_typeid: TypeId,
|
||||||
|
pub any_vtable: *const (),
|
||||||
|
pub traits: HashMap<TypeId, *const ()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MultitraitObject {
|
||||||
|
pub fn new<T: 'static + Any>(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::<T>(),
|
||||||
|
traits: Default::default(),
|
||||||
|
};
|
||||||
|
this._register::<dyn Any>(any_vtable);
|
||||||
|
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downcasts the object into a reference of the given trait
|
||||||
|
pub fn downcast_trait<T1: 'static + ?Sized>(&self) -> Option<&T1> {
|
||||||
|
if std::mem::size_of::<&T1>() != std::mem::size_of::<FatPointer>() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
unsafe {
|
||||||
|
self._downcast_trait::<T1>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downcasts the object into a mutable reference of the given trait
|
||||||
|
pub fn downcast_trait_mut<T1: 'static + ?Sized>(&mut self) -> Option<&mut T1> {
|
||||||
|
if std::mem::size_of::<&T1>() != std::mem::size_of::<FatPointer>() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
unsafe {
|
||||||
|
self._downcast_trait_mut::<T1>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downcasts the object to a boxed representation of the given trait
|
||||||
|
pub fn downcast_trait_boxed<T1: 'static + ?Sized>(mut self) -> Option<Box<T1>> {
|
||||||
|
if std::mem::size_of::<&T1>() != std::mem::size_of::<FatPointer>() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
unsafe {
|
||||||
|
self._downcast_boxed_trait::<T1>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downcasts the object into a reference of the given type
|
||||||
|
pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
|
||||||
|
let any = self.downcast_trait::<dyn Any>().unwrap();
|
||||||
|
any.downcast_ref::<T>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downcasts the object into a mutable reference of the given type
|
||||||
|
pub fn downcast_mut<T: Any>(&mut self) -> Option<&mut T> {
|
||||||
|
let any = self.downcast_trait_mut::<dyn Any>().unwrap();
|
||||||
|
any.downcast_mut::<T>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downcasts the object into the given concrete type
|
||||||
|
pub fn downcast<T: Any>(self) -> Option<T> {
|
||||||
|
if TypeId::of::<T>() == 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<T2: 'static + ?Sized>(&mut self, vtable_ptr: *const ()) {
|
||||||
|
self.traits.insert(TypeId::of::<T2>(), vtable_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
unsafe fn _downcast_trait<T1: 'static + ?Sized>(&self) -> Option<&T1> {
|
||||||
|
let vptr = *self.traits.get(&TypeId::of::<T1>())?;
|
||||||
|
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<T1: 'static + ?Sized>(&mut self) -> Option<&mut T1> {
|
||||||
|
let vptr = *self.traits.get(&TypeId::of::<T1>())?;
|
||||||
|
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<T1: 'static + ?Sized>(&mut self) -> Option<Box<T1>> {
|
||||||
|
let vptr = *self.traits.get(&TypeId::of::<T1>())?;
|
||||||
|
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::*;
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
}
|
@ -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::<dyn Debug>().unwrap();
|
||||||
|
let _ = format!("{:?}", debug);
|
||||||
|
let obj = mto.downcast_trait::<dyn DynClone>().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::<dyn ChangeStruct>().unwrap();
|
||||||
|
change_struct.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_downcasts_boxed_traits() {
|
||||||
|
let mto = TestStruct::default().into_multitrait();
|
||||||
|
let boxed = mto.downcast_trait_boxed::<dyn Debug>().unwrap();
|
||||||
|
let _ = format!("{:?}", boxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_downcasts_to_original() {
|
||||||
|
let mut mto = TestStruct::default().into_multitrait();
|
||||||
|
{
|
||||||
|
mto.downcast_ref::<TestStruct>().unwrap();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
mto.downcast_mut::<TestStruct>().unwrap();
|
||||||
|
}
|
||||||
|
let result = mto.downcast::<TestStruct>().unwrap();
|
||||||
|
assert_eq!(result.a, 5);
|
||||||
|
assert_eq!(result.test, String::from("Hello World"));
|
||||||
|
}
|
Loading…
Reference in New Issue