From 155018359804881bb434254b0bdbab8ee943b3ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Thu, 2 May 2024 20:24:58 +0200 Subject: [PATCH 01/11] README fix from review --- miniconf/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/miniconf/README.md b/miniconf/README.md index e816c0e3..c86b381c 100644 --- a/miniconf/README.md +++ b/miniconf/README.md @@ -18,7 +18,7 @@ which is a comprehensive and mature reflection crate: `bevy_reflect` uses its `Reflect` trait to operate on and pass nodes as trait objects. `miniconf` uses serialized data or `Any` to access leaf nodes and pure "code" to traverse through internal nodes. The `Tree*` traits like `Reflect` thus give access to nodes but unlike `Reflect` they are all decidedly not object-safe -and can not used as trait objects. This allows `miniconf` to support non-`'static` borrowed data +and can not be used as trait objects. This allows `miniconf` to support non-`'static` borrowed data (only for `TreeAny` the leaf nodes need to be `'static`) while `bevy_reflect` requires `'static` for `Reflect` types. From 45532d865902ba508018a967203d498d330ef0f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Thu, 2 May 2024 15:52:15 +0200 Subject: [PATCH 02/11] expand reflect example --- Cargo.toml | 1 + miniconf/Cargo.toml | 5 ++ miniconf/examples/reflect.rs | 102 +++++++++++++++++++++-------------- 3 files changed, 68 insertions(+), 40 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 61942016..8823fcec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ + # "crosstrait", "miniconf_derive", "miniconf", "miniconf_mqtt", diff --git a/miniconf/Cargo.toml b/miniconf/Cargo.toml index 40543ecf..49630a97 100644 --- a/miniconf/Cargo.toml +++ b/miniconf/Cargo.toml @@ -31,6 +31,11 @@ all-features = true [dev-dependencies] postcard = { version = "1.0.8", features = ["use-std"] } serde = { version = "1.0.120", features = ["derive"] } +erased-serde = "0.4" +linkme = "0.3" +heapless = "0.8" +gensym = "0.1" +once_cell = "1.19" [[test]] name = "arrays" diff --git a/miniconf/examples/reflect.rs b/miniconf/examples/reflect.rs index 69b8df9f..1c5979b9 100644 --- a/miniconf/examples/reflect.rs +++ b/miniconf/examples/reflect.rs @@ -1,5 +1,7 @@ use core::any::{Any, TypeId}; +use heapless::FnvIndexMap; use miniconf::{JsonPath, TreeAny, TreeKey}; +use once_cell::sync::Lazy; #[non_exhaustive] pub struct Caster { @@ -7,35 +9,42 @@ pub struct Caster { pub mut_: fn(from: &mut dyn Any) -> Option<&mut T>, } -macro_rules! casters { - ( $( $ty:ty ),+ => $tr:ty ) => { ( - TypeId::of::<$tr>(), - &[ $( ( - TypeId::of::<$ty>(), - &Caster::<$tr> { - ref_: |any| any.downcast_ref::<$ty>().map(|t| t as _), - mut_: |any| any.downcast_mut::<$ty>().map(|t| t as _), - }, - ) ),+ ], - ) }; -} +type Entry = ([TypeId; 2], &'static (dyn Any + Send + Sync)); + +#[::linkme::distributed_slice] +static __REGISTRY: [fn() -> Entry]; -pub struct Registry<'a> { - // use a suitable hashmap (maybe phf+linkme when TypeId::of() is const), - // boxes, vecs, lazy caster creation, and a static where possible - casters: &'a [(TypeId, &'a [(TypeId, &'a dyn Any)])], +macro_rules! register { + ( $ty:ty, $tr:ty ) => { + register! { __REGISTRY, $ty, $tr } + }; + ( $reg:ident, $ty:ty, $tr:ty ) => { + ::gensym::gensym! { register!{ $reg, $ty, $tr } } + }; + ( $name:ident, $reg:ident, $ty:ty, $tr:ty ) => { + #[::linkme::distributed_slice($reg)] + fn $name() -> Entry { + ( + [ + ::core::any::TypeId::of::<$tr>(), + ::core::any::TypeId::of::<$ty>(), + ], + &Caster::<$tr> { + ref_: |any| any.downcast_ref::<$ty>().map(|t| t as _), + mut_: |any| any.downcast_mut::<$ty>().map(|t| t as _), + }, + ) + } + }; } -impl<'a> Registry<'a> { +pub struct Registry(FnvIndexMap<[TypeId; 2], &'static (dyn Any + Send + Sync), N>); + +impl Registry { pub fn caster(&self, any: &dyn Any) -> Option<&Caster> { - let target = TypeId::of::(); - let (_, types) = self - .casters - .iter() - .find(|(trait_id, _)| trait_id == &target)?; - let source = any.type_id(); - let (_, caster) = types.iter().find(|(type_id, _)| type_id == &source)?; - caster.downcast_ref() + self.0 + .get(&[TypeId::of::(), any.type_id()])? + .downcast_ref() } pub fn implements(&self, any: &dyn Any) -> bool { @@ -51,6 +60,32 @@ impl<'a> Registry<'a> { } } +static REGISTRY: Lazy> = + Lazy::new(|| Registry(__REGISTRY.iter().map(|maker| maker()).collect())); + +trait Cast { + fn cast(self) -> Option; +} + +impl<'a, T: ?Sized + 'static> Cast<&'a T> for &'a dyn Any { + fn cast(self) -> Option<&'a T> { + REGISTRY.cast_ref(self) + } +} + +impl<'a, T: ?Sized + 'static> Cast<&'a mut T> for &'a mut dyn Any { + fn cast(self) -> Option<&'a mut T> { + REGISTRY.cast_mut(self) + } +} + +use core::fmt::Debug; +use core::ops::AddAssign; + +register! { u8, dyn Debug } +register! { i32, dyn AddAssign + Sync } +register! { i32, dyn erased_serde::Serialize } + #[derive(TreeKey, TreeAny, Default)] struct Inner { a: u8, @@ -64,28 +99,15 @@ struct Settings { } fn main() { - use core::fmt::{Debug, Formatter, Write}; - use core::ops::AddAssign; - let registry = Registry { - casters: &[ - casters!(u8, i32, String, &[u8], &str => dyn Debug), - casters!(String, Formatter => dyn Write), - casters!(i32 => dyn AddAssign + Sync), - // casters!(erased_serde::Serialize => u8, i32, String, Vec), - ], - }; - let mut s = Settings::default(); s.i[1].a = 9; let key: JsonPath = ".i[1].a".into(); - let a_any = s.ref_any_by_key(key).unwrap(); - let a: &dyn Debug = registry.cast_ref(a_any).unwrap(); + let a: &dyn Debug = s.ref_any_by_key(key).unwrap().cast().unwrap(); println!("{a:?}"); let key: JsonPath = ".v".into(); - let v_any = s.mut_any_by_key(key).unwrap(); - let v: &mut (dyn AddAssign + Sync) = registry.cast_mut(v_any).unwrap(); + let v: &mut (dyn AddAssign + Sync) = s.mut_any_by_key(key).unwrap().cast().unwrap(); *v += 3; assert_eq!(s.v, 3); } From cd50d7149585bd0a499235d0f78de8dce1957fd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Thu, 2 May 2024 16:10:54 +0200 Subject: [PATCH 03/11] add crosstrait --- Cargo.toml | 2 +- crosstrait/Cargo.toml | 23 ++++ crosstrait/README.md | 97 ++++++++++++++ crosstrait/src/lib.rs | 253 +++++++++++++++++++++++++++++++++++ miniconf/Cargo.toml | 9 +- miniconf/examples/reflect.rs | 90 +------------ 6 files changed, 383 insertions(+), 91 deletions(-) create mode 100644 crosstrait/Cargo.toml create mode 100644 crosstrait/README.md create mode 100644 crosstrait/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 8823fcec..594a488d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] members = [ - # "crosstrait", + "crosstrait", "miniconf_derive", "miniconf", "miniconf_mqtt", diff --git a/crosstrait/Cargo.toml b/crosstrait/Cargo.toml new file mode 100644 index 00000000..fc3495b5 --- /dev/null +++ b/crosstrait/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "crosstrait" +version = "0.1.0" +edition = "2021" +authors = ["Robert Jördens "] +license = "MIT OR Apache-2.0" +description = "Cast from `dyn Any` to other trait objects, with no_std, no alloc support" +repository = "https://github.com/quartiq/miniconf" +documentation = "https://docs.rs/crosstrait" +readme = "README.md" +categories = ["rust-patterns", "no-std", "no-std::no-alloc"] +keywords = ["linkage", "trait", "cast", "any"] + +[dependencies] +linkme = "0.3" +heapless = "0.8" +gensym = "0.1" +once_cell = { version = "1.19", default-features = false, features = ["critical-section"] } + +[features] +std = ["alloc", "once_cell/std"] +alloc = [] +default = ["std"] diff --git a/crosstrait/README.md b/crosstrait/README.md new file mode 100644 index 00000000..d31ab4ea --- /dev/null +++ b/crosstrait/README.md @@ -0,0 +1,97 @@ +# Cast from `dyn Any` to other trait objects + +* `no_std` no alloc support +* No proc macros + +## Usage + +```toml +[dependencies] +crosstrait = "0.1" +``` + +Then use the `register!{}` declarative macro and the [`Cast`] traits. + +For embedded, the linker needs to be informed of the type registry. + +## Example + +```rust +use core::any::Any; +use crosstrait::{register, Cast, Castable, CastableRef}; + +// Some example traits +use core::{fmt::{Debug, Formatter, Write}, ops::AddAssign}; + +// Add types and implementations in the default global registy. +// Implementation status is verified at compile time +register! { u8 => dyn Debug } +// Auto-traits are distinct and must be explicitly registered +register! { i32 => dyn AddAssign + Sync } +// If a type is not Send + Sync, it can't cast as Arc: +register! { Formatter => dyn Write, no_arc } +// Registering foreign types and traits works fine +register! { String => dyn Write } +// Serialization/deserialization of `dyn Any` is a major use case. +// register! { i32 => dyn erased_serde::Serialize } + +fn main() { + // Check for trait impl registration + let any: &dyn Any = &0u8; + assert!(any.castable::()); + // AddAssign is not registered for u8 + assert!(!any.castable::>()); + // Check based on type + assert!(u8::castable::()); + + // Cast ref + let a: &dyn Debug = any.cast().unwrap(); + println!("{a:?}"); + + // Autotraits are distinct + assert!(Cast::<&dyn AddAssign>::cast(any).is_none()); + + // Cast mut + let mut value = 5i32; + let any: &mut dyn Any = &mut value; + let v: &mut (dyn AddAssign + Sync) = any.cast().unwrap(); + *v += 3; + assert_eq!(value, 5 + 3); + + // Cast Box + let any: Box = Box::new(0u8); + let _: Box = any.cast().unwrap(); + + // Cast Rc + use std::rc::Rc; + let any: Rc = Rc::new(0u8); + let _: Rc = any.cast().unwrap(); + + // Cast Arc + use std::sync::Arc; + let any: Arc = Arc::new(0u8); + let _: Arc = any.cast().unwrap(); + + // Use an explicit registry + crosstrait::REGISTRY.cast_ref::(&0u8 as &dyn Any).unwrap(); +} +``` + +## Related crates + +* [`intertrait`](https://crates.io/crates/intertrait): similar goals, `std` +* [`miniconf`](https://crates.io/crates/miniconf): provides several ways to get `dyn Any` from heterogeneous + nested data structures, `no_std`, no alloc +* [`erased_serde`](https://crates.io/crates/erased-serde): serialization on trait objects, serializer/deserializer trait objects +* [`linkme`](https://crates.io/crates/linkme): linker magic to build distributed static slices + +## Limitations + +### Registry size on `no_std` + +Currently the size of the global registry on `no_std` is fixed and arbitrarily set to 128 entries. + +### Auto traits + +Since adding any combination of auto traits (in particular `Send`, `Sync`, `Unpin`) to a trait results in a distinct trait, +all relevant combinations of traits plus auto traits needs to be registered explicitly. diff --git a/crosstrait/src/lib.rs b/crosstrait/src/lib.rs new file mode 100644 index 00000000..325b62af --- /dev/null +++ b/crosstrait/src/lib.rs @@ -0,0 +1,253 @@ +#![cfg_attr(not(any(test, doctest, feature = "std")), no_std)] +#![cfg_attr(feature = "std", doc = include_str!("../README.md"))] + +use core::any::{Any, TypeId}; + +#[cfg(feature = "alloc")] +extern crate alloc; +#[cfg(feature = "alloc")] +use alloc::boxed::Box; +#[cfg(feature = "std")] +use std::{rc::Rc, sync::Arc}; + +use once_cell::sync::Lazy; + +// Re-exports for `register!()` macro +#[doc(hidden)] +pub use gensym::gensym; +#[doc(hidden)] +pub use linkme; + +/// Contains all relevant casting functions for a type-trait pair +/// The casting path is (conceptually) `Any::downcast::() as Target`. +/// +/// The intermediate concrete type must be consistent. +#[doc(hidden)] +#[allow(clippy::type_complexity)] +pub struct Caster { + pub ref_: fn(&dyn Any) -> Option<&T>, + pub mut_: fn(&mut dyn Any) -> Option<&mut T>, + #[cfg(feature = "alloc")] + pub box_: fn(Box) -> Result, Box>, + #[cfg(feature = "std")] + pub rc: fn(Rc) -> Result, Rc>, + #[cfg(feature = "std")] + pub arc: fn(Arc) -> Result, Arc>, +} + +/// Key-value pair of the type registry +/// The caster is a static ref to a trait object since its type +/// varies with the target trait. +#[doc(hidden)] +pub type Entry = ([TypeId; 2], &'static (dyn Any + Send + Sync)); + +/// Static slice of key and caster maker functions. +/// `TypeId::of()` is not const +/// But luckily we can make the Caster 'static. +#[doc(hidden)] +#[linkme::distributed_slice] +pub static __REGISTRY: [fn() -> Entry]; + +/// Register a type and target trait in the registry. +#[macro_export] +macro_rules! register { + ( $ty:ty => $tr:ty $(, $flag:ident)? ) => { + $crate::register! { $crate::__REGISTRY: $ty => $tr $(, $flag)? } + }; + ( $reg:path: $ty:ty => $tr:ty $(, $flag:ident)? ) => { + $crate::gensym! { $crate::register!{ $reg: $ty => $tr $(, $flag)? } } + }; + ( $name:ident, $reg:path: $ty:ty => $tr:ty $(, $flag:ident)? ) => { + #[$crate::linkme::distributed_slice($reg)] + #[linkme(crate=$crate::linkme)] + fn $name() -> $crate::Entry { + ( + [ + ::core::any::TypeId::of::<$tr>(), + ::core::any::TypeId::of::<$ty>(), + ], + &$crate::caster!($ty => $tr $(, $flag)? ), + ) + } + }; +} + +/// Build a `Caster` for a given concrete type and target trait +#[cfg(all(feature = "alloc", not(feature = "std")))] +#[macro_export] +macro_rules! caster { + ( $ty:ty => $tr:ty $(, $flag:ident)? ) => { + $crate::Caster::<$tr> { + ref_: |any| any.downcast_ref::<$ty>().map(|t| t as _), + mut_: |any| any.downcast_mut::<$ty>().map(|t| t as _), + box_: |any| any.downcast::<$ty>().map(|t| t as _), + } + }; +} +/// Build a `Caster` for a given concrete type and target trait +#[cfg(all(not(feature = "alloc"), not(feature = "std")))] +#[macro_export] +macro_rules! caster { + ( $ty:ty => $tr:ty $(, $flag:ident)? ) => { + $crate::Caster::<$tr> { + ref_: |any| any.downcast_ref::<$ty>().map(|t| t as _), + mut_: |any| any.downcast_mut::<$ty>().map(|t| t as _), + } + }; +} +/// Build a `Caster` for a given concrete type and target trait +#[cfg(feature = "std")] +#[macro_export] +macro_rules! caster { + ( $ty:ty => $tr:ty $(, $flag:ident)? ) => { + $crate::Caster::<$tr> { + ref_: |any| any.downcast_ref::<$ty>().map(|t| t as _), + mut_: |any| any.downcast_mut::<$ty>().map(|t| t as _), + box_: |any| any.downcast::<$ty>().map(|t| t as _), + rc: |any| any.downcast::<$ty>().map(|t| t as _), + arc: $crate::caster!( $ty $(, $flag)? ), + } + }; + ( $ty:ty ) => { + |any| any.downcast::<$ty>().map(|t| t as _) + }; + ( $ty:ty, $flag:ident ) => { + |any| Err(any) + }; +} + +// TODO: fixed size map +// Note that each key is size_of([TypeId; 2]) = 32 bytes. +// Maybe risk type collisions and reduce key size. +#[cfg(not(feature = "std"))] +type RegistryMap = heapless::FnvIndexMap<[TypeId; 2], &'static (dyn Any + Send + Sync), { 1 << 7 }>; + +#[cfg(feature = "std")] +type RegistryMap = std::collections::HashMap<[TypeId; 2], &'static (dyn Any + Send + Sync)>; + +/// The type-trait registry. +#[derive(Default, Debug, Clone)] +pub struct Registry(pub RegistryMap); + +impl Registry { + /// Obtain a `Caster` given the TypeId for the concrete type and a target trait `T`. + fn caster(&self, any: TypeId) -> Option<&Caster> { + self.0.get(&[TypeId::of::(), any])?.downcast_ref() + } + + /// Whether the `Any` can be cast to the target trait. + pub fn castable_ref(&self, any: &dyn Any) -> bool { + self.0.contains_key(&[TypeId::of::(), any.type_id()]) + } + + /// Whether the concrete type can be case to the target trait. + pub fn castable(&self) -> bool { + self.0.contains_key(&[TypeId::of::(), TypeId::of::()]) + } + + /// Cast to an immuatbly borrowed trait object. + pub fn cast_ref<'b, T: ?Sized + 'static>(&self, any: &'b dyn Any) -> Option<&'b T> { + (self.caster(any.type_id())?.ref_)(any) + } + + /// Cast to a mutably borrowed trait object. + pub fn cast_mut<'b, T: ?Sized + 'static>(&self, any: &'b mut dyn Any) -> Option<&'b mut T> { + (self.caster((*any).type_id())?.mut_)(any) + } + + /// Cast to a boxed trait object. + #[cfg(feature = "alloc")] + pub fn cast_box(&self, any: Box) -> Result, Box> { + match self.caster((*any).type_id()) { + Some(c) => (c.box_)(any), + None => Err(any), + } + } + + /// Cast to a ref counted trait object. + #[cfg(feature = "std")] + pub fn cast_rc(&self, any: Rc) -> Result, Rc> { + match self.caster((*any).type_id()) { + Some(c) => (c.rc)(any), + None => Err(any), + } + } + + /// Cast to an atomically ref counted trait object. + #[cfg(feature = "std")] + pub fn cast_arc( + &self, + any: Arc, + ) -> Result, Arc> { + match self.caster((*any).type_id()) { + Some(c) => (c.arc)(any), + None => Err(any), + } + } +} + +/// The global type registry +pub static REGISTRY: Lazy = + Lazy::new(|| Registry(__REGISTRY.iter().map(|maker| maker()).collect())); + +/// Capability to determine whether we can be cast to a given trait object. +pub trait CastableRef { + /// Whether we can be cast to a given trait object. + fn castable(self) -> bool; +} + +impl CastableRef for &dyn Any { + fn castable(self) -> bool { + REGISTRY.castable_ref::(self) + } +} + +/// Whether `Any` trait objects of this concrete type can be cast to a given trait object. +pub trait Castable { + /// This type is castable to the given trait object. + fn castable() -> bool; +} + +impl Castable for U { + fn castable() -> bool { + REGISTRY.castable::() + } +} + +/// Cast an `Any` trait object to another trait object. +pub trait Cast { + fn cast(self) -> Option; +} + +impl<'a, T: ?Sized + 'static> Cast<&'a T> for &'a dyn Any { + fn cast(self) -> Option<&'a T> { + REGISTRY.cast_ref(self) + } +} + +impl<'a, T: ?Sized + 'static> Cast<&'a mut T> for &'a mut dyn Any { + fn cast(self) -> Option<&'a mut T> { + REGISTRY.cast_mut(self) + } +} + +#[cfg(feature = "std")] +impl Cast> for Box { + fn cast(self) -> Option> { + REGISTRY.cast_box(self).ok() + } +} + +#[cfg(feature = "std")] +impl Cast> for Rc { + fn cast(self) -> Option> { + REGISTRY.cast_rc(self).ok() + } +} + +#[cfg(feature = "std")] +impl Cast> for Arc { + fn cast(self) -> Option> { + REGISTRY.cast_arc(self).ok() + } +} diff --git a/miniconf/Cargo.toml b/miniconf/Cargo.toml index 49630a97..87818efb 100644 --- a/miniconf/Cargo.toml +++ b/miniconf/Cargo.toml @@ -31,11 +31,8 @@ all-features = true [dev-dependencies] postcard = { version = "1.0.8", features = ["use-std"] } serde = { version = "1.0.120", features = ["derive"] } -erased-serde = "0.4" -linkme = "0.3" -heapless = "0.8" -gensym = "0.1" -once_cell = "1.19" +crosstrait = { version = "0.1", path = "../crosstrait", default-features = false } +erased-serde = "0.4.4" [[test]] name = "arrays" @@ -79,4 +76,4 @@ required-features = ["json-core", "derive"] [[example]] name = "reflect" -required-features = ["json-core", "derive"] +required-features = ["derive"] diff --git a/miniconf/examples/reflect.rs b/miniconf/examples/reflect.rs index 1c5979b9..83df1f9e 100644 --- a/miniconf/examples/reflect.rs +++ b/miniconf/examples/reflect.rs @@ -1,90 +1,12 @@ -use core::any::{Any, TypeId}; -use heapless::FnvIndexMap; -use miniconf::{JsonPath, TreeAny, TreeKey}; -use once_cell::sync::Lazy; - -#[non_exhaustive] -pub struct Caster { - pub ref_: fn(from: &dyn Any) -> Option<&T>, - pub mut_: fn(from: &mut dyn Any) -> Option<&mut T>, -} - -type Entry = ([TypeId; 2], &'static (dyn Any + Send + Sync)); - -#[::linkme::distributed_slice] -static __REGISTRY: [fn() -> Entry]; - -macro_rules! register { - ( $ty:ty, $tr:ty ) => { - register! { __REGISTRY, $ty, $tr } - }; - ( $reg:ident, $ty:ty, $tr:ty ) => { - ::gensym::gensym! { register!{ $reg, $ty, $tr } } - }; - ( $name:ident, $reg:ident, $ty:ty, $tr:ty ) => { - #[::linkme::distributed_slice($reg)] - fn $name() -> Entry { - ( - [ - ::core::any::TypeId::of::<$tr>(), - ::core::any::TypeId::of::<$ty>(), - ], - &Caster::<$tr> { - ref_: |any| any.downcast_ref::<$ty>().map(|t| t as _), - mut_: |any| any.downcast_mut::<$ty>().map(|t| t as _), - }, - ) - } - }; -} - -pub struct Registry(FnvIndexMap<[TypeId; 2], &'static (dyn Any + Send + Sync), N>); - -impl Registry { - pub fn caster(&self, any: &dyn Any) -> Option<&Caster> { - self.0 - .get(&[TypeId::of::(), any.type_id()])? - .downcast_ref() - } - - pub fn implements(&self, any: &dyn Any) -> bool { - self.caster::(any).is_some() - } - - pub fn cast_ref<'b, T: ?Sized + 'static>(&self, any: &'b dyn Any) -> Option<&'b T> { - (self.caster(any)?.ref_)(any) - } - - pub fn cast_mut<'b, T: ?Sized + 'static>(&self, any: &'b mut dyn Any) -> Option<&'b mut T> { - (self.caster(any)?.mut_)(any) - } -} - -static REGISTRY: Lazy> = - Lazy::new(|| Registry(__REGISTRY.iter().map(|maker| maker()).collect())); - -trait Cast { - fn cast(self) -> Option; -} - -impl<'a, T: ?Sized + 'static> Cast<&'a T> for &'a dyn Any { - fn cast(self) -> Option<&'a T> { - REGISTRY.cast_ref(self) - } -} - -impl<'a, T: ?Sized + 'static> Cast<&'a mut T> for &'a mut dyn Any { - fn cast(self) -> Option<&'a mut T> { - REGISTRY.cast_mut(self) - } -} - use core::fmt::Debug; use core::ops::AddAssign; +use crosstrait::{register, Cast}; + +use miniconf::{JsonPath, TreeAny, TreeKey}; -register! { u8, dyn Debug } -register! { i32, dyn AddAssign + Sync } -register! { i32, dyn erased_serde::Serialize } +register! { u8 => dyn Debug } +register! { i32 => dyn AddAssign + Sync } +register! { i32 => dyn erased_serde::Serialize } #[derive(TreeKey, TreeAny, Default)] struct Inner { From f628f119d24751656ad33727b8e0bd5141d0d110 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Thu, 2 May 2024 23:29:55 +0200 Subject: [PATCH 04/11] crosstrait work --- crosstrait/Cargo.toml | 4 +- crosstrait/README.md | 120 ++++++++++++++++++++++-------------------- crosstrait/src/lib.rs | 103 ++++++++++++++++++++---------------- 3 files changed, 123 insertions(+), 104 deletions(-) diff --git a/crosstrait/Cargo.toml b/crosstrait/Cargo.toml index fc3495b5..4e963f5b 100644 --- a/crosstrait/Cargo.toml +++ b/crosstrait/Cargo.toml @@ -8,12 +8,12 @@ description = "Cast from `dyn Any` to other trait objects, with no_std, no alloc repository = "https://github.com/quartiq/miniconf" documentation = "https://docs.rs/crosstrait" readme = "README.md" -categories = ["rust-patterns", "no-std", "no-std::no-alloc"] +categories = ["rust-patterns", "embedded", "no-std", "no-std::no-alloc"] keywords = ["linkage", "trait", "cast", "any"] [dependencies] linkme = "0.3" -heapless = "0.8" +heapless = "0.8" # not(feature = "std") gensym = "0.1" once_cell = { version = "1.19", default-features = false, features = ["critical-section"] } diff --git a/crosstrait/README.md b/crosstrait/README.md index d31ab4ea..6bd7a040 100644 --- a/crosstrait/README.md +++ b/crosstrait/README.md @@ -10,7 +10,7 @@ crosstrait = "0.1" ``` -Then use the `register!{}` declarative macro and the [`Cast`] traits. +Then use the `register!{ Type => Trait }` declarative macro and the [`Cast`] traits. For embedded, the linker needs to be informed of the type registry. @@ -18,72 +18,76 @@ For embedded, the linker needs to be informed of the type registry. ```rust use core::any::Any; -use crosstrait::{register, Cast, Castable, CastableRef}; +use crosstrait::{Cast, Castable, CastableRef, register, REGISTRY}; -// Some example traits -use core::{fmt::{Debug, Formatter, Write}, ops::AddAssign}; +// Some example traits to play with +use core::{fmt::{Debug, Formatter, Write}, ops::{AddAssign, SubAssign}}; -// Add types and implementations in the default global registy. +// Add types and trait implementations in the default global registy // Implementation status is verified at compile time -register! { u8 => dyn Debug } -// Auto-traits are distinct and must be explicitly registered -register! { i32 => dyn AddAssign + Sync } -// If a type is not Send + Sync, it can't cast as Arc: -register! { Formatter => dyn Write, no_arc } +register!{ i32 => dyn Debug } + // Registering foreign types and traits works fine -register! { String => dyn Write } -// Serialization/deserialization of `dyn Any` is a major use case. +// Serialization/deserialization of `dyn Any` is a major use case // register! { i32 => dyn erased_serde::Serialize } -fn main() { - // Check for trait impl registration - let any: &dyn Any = &0u8; - assert!(any.castable::()); - // AddAssign is not registered for u8 - assert!(!any.castable::>()); - // Check based on type - assert!(u8::castable::()); - - // Cast ref - let a: &dyn Debug = any.cast().unwrap(); - println!("{a:?}"); - - // Autotraits are distinct - assert!(Cast::<&dyn AddAssign>::cast(any).is_none()); - - // Cast mut - let mut value = 5i32; - let any: &mut dyn Any = &mut value; - let v: &mut (dyn AddAssign + Sync) = any.cast().unwrap(); - *v += 3; - assert_eq!(value, 5 + 3); - - // Cast Box - let any: Box = Box::new(0u8); - let _: Box = any.cast().unwrap(); - - // Cast Rc - use std::rc::Rc; - let any: Rc = Rc::new(0u8); - let _: Rc = any.cast().unwrap(); - - // Cast Arc - use std::sync::Arc; - let any: Arc = Arc::new(0u8); - let _: Arc = any.cast().unwrap(); - - // Use an explicit registry - crosstrait::REGISTRY.cast_ref::(&0u8 as &dyn Any).unwrap(); -} +// Check for trait impl registration on concrete type +assert!(i32::castable::()); + +// Check for trait impl registration on Any +let any: &dyn Any = &42i32; +assert!(any.castable::()); + +// SubAssign is impl'd for i32 but not registered +assert!(!any.castable::>()); + +// Cast ref +let a: &dyn Debug = any.cast().unwrap(); +println!("42 = {a:?}"); + +// Cast mut +let mut value = 5i32; +let any: &mut dyn Any = &mut value; +let v: &mut dyn AddAssign = any.cast().unwrap(); +*v += 3; +assert_eq!(value, 5 + 3); + +// Cast Box +let any: Box = Box::new(0i32); +let _: Box = any.cast().unwrap(); + +// Cast Rc +use std::rc::Rc; +let any: Rc = Rc::new(0i32); +let _: Rc = any.cast().unwrap(); + +// Cast Arc +use std::sync::Arc; +let any: Arc = Arc::new(0i32); +let _: Arc = any.cast().unwrap(); + +// Explicit registry usage +let any: &dyn Any = &0i32; +let _: &dyn Debug = REGISTRY.cast_ref(any).unwrap(); + +// Autotraits and type/const generics are distinct +let a: Option<&(dyn Debug + Sync)> = any.cast(); +assert!(a.is_none()); + +// Registration can happen anywhere in any order in any downstream crate +register!{ i32 => dyn AddAssign } + +// If a type is not Send + Sync, it can't cast as Arc. `no_arc` accounts for that +register!{ Formatter => dyn Write, no_arc } ``` ## Related crates * [`intertrait`](https://crates.io/crates/intertrait): similar goals, `std` -* [`miniconf`](https://crates.io/crates/miniconf): provides several ways to get `dyn Any` from heterogeneous - nested data structures, `no_std`, no alloc -* [`erased_serde`](https://crates.io/crates/erased-serde): serialization on trait objects, serializer/deserializer trait objects -* [`linkme`](https://crates.io/crates/linkme): linker magic to build distributed static slices +* [`miniconf`](https://crates.io/crates/miniconf): provides several ways to get `dyn Any` from nodes in + heterogeneous nested data structures, `no_std`, no alloc +* [`erased_serde`](https://crates.io/crates/erased-serde): `Serialize`/`Serializer`/`Deserializer` trait objects +* [`linkme`](https://crates.io/crates/linkme): linker magic used to build distributed static type registry ## Limitations @@ -95,3 +99,7 @@ Currently the size of the global registry on `no_std` is fixed and arbitrarily s Since adding any combination of auto traits (in particular `Send`, `Sync`, `Unpin`) to a trait results in a distinct trait, all relevant combinations of traits plus auto traits needs to be registered explicitly. + +### Global registry + +A custom non-static [`Registry`] can be built and used explicitly but the `Cast` traits will not use it. diff --git a/crosstrait/src/lib.rs b/crosstrait/src/lib.rs index 325b62af..b6542784 100644 --- a/crosstrait/src/lib.rs +++ b/crosstrait/src/lib.rs @@ -24,6 +24,7 @@ pub use linkme; /// The intermediate concrete type must be consistent. #[doc(hidden)] #[allow(clippy::type_complexity)] +#[derive(Clone, Debug, PartialEq)] pub struct Caster { pub ref_: fn(&dyn Any) -> Option<&T>, pub mut_: fn(&mut dyn Any) -> Option<&mut T>, @@ -35,20 +36,21 @@ pub struct Caster { pub arc: fn(Arc) -> Result, Arc>, } -/// Key-value pair of the type registry +/// Key-value pair of the compiled type registry +/// `TypeId::of()` is not const (https://github.com/rust-lang/rust/issues/77125) +/// Hence we store a maker fn and call lazily. +/// Once it is const, remove the `fn() ->` and `||` and investigate phf. /// The caster is a static ref to a trait object since its type /// varies with the target trait. #[doc(hidden)] -pub type Entry = ([TypeId; 2], &'static (dyn Any + Send + Sync)); +pub type Entry = (fn() -> [TypeId; 2], &'static (dyn Any + Send + Sync)); -/// Static slice of key and caster maker functions. -/// `TypeId::of()` is not const -/// But luckily we can make the Caster 'static. +/// Static slice of key maker fns and Caster trait objects #[doc(hidden)] #[linkme::distributed_slice] -pub static __REGISTRY: [fn() -> Entry]; +pub static __REGISTRY: [Entry]; -/// Register a type and target trait in the registry. +/// Register a type and target trait in the registry #[macro_export] macro_rules! register { ( $ty:ty => $tr:ty $(, $flag:ident)? ) => { @@ -60,44 +62,42 @@ macro_rules! register { ( $name:ident, $reg:path: $ty:ty => $tr:ty $(, $flag:ident)? ) => { #[$crate::linkme::distributed_slice($reg)] #[linkme(crate=$crate::linkme)] - fn $name() -> $crate::Entry { - ( - [ - ::core::any::TypeId::of::<$tr>(), - ::core::any::TypeId::of::<$ty>(), - ], - &$crate::caster!($ty => $tr $(, $flag)? ), - ) - } + static $name: $crate::Entry = ( + || [::core::any::TypeId::of::<$tr>(), ::core::any::TypeId::of::<$ty>()], + &$crate::caster!( $ty => $tr $(, $flag)? ), + ); }; } /// Build a `Caster` for a given concrete type and target trait -#[cfg(all(feature = "alloc", not(feature = "std")))] +#[cfg(all(not(feature = "alloc"), not(feature = "std")))] #[macro_export] +#[doc(hidden)] macro_rules! caster { ( $ty:ty => $tr:ty $(, $flag:ident)? ) => { $crate::Caster::<$tr> { ref_: |any| any.downcast_ref::<$ty>().map(|t| t as _), mut_: |any| any.downcast_mut::<$ty>().map(|t| t as _), - box_: |any| any.downcast::<$ty>().map(|t| t as _), } }; } /// Build a `Caster` for a given concrete type and target trait -#[cfg(all(not(feature = "alloc"), not(feature = "std")))] +#[cfg(all(feature = "alloc", not(feature = "std")))] #[macro_export] +#[doc(hidden)] macro_rules! caster { ( $ty:ty => $tr:ty $(, $flag:ident)? ) => { $crate::Caster::<$tr> { ref_: |any| any.downcast_ref::<$ty>().map(|t| t as _), mut_: |any| any.downcast_mut::<$ty>().map(|t| t as _), + box_: |any| any.downcast::<$ty>().map(|t| t as _), } }; } /// Build a `Caster` for a given concrete type and target trait #[cfg(feature = "std")] #[macro_export] +#[doc(hidden)] macro_rules! caster { ( $ty:ty => $tr:ty $(, $flag:ident)? ) => { $crate::Caster::<$tr> { @@ -111,51 +111,53 @@ macro_rules! caster { ( $ty:ty ) => { |any| any.downcast::<$ty>().map(|t| t as _) }; - ( $ty:ty, $flag:ident ) => { + ( $ty:ty, no_arc ) => { |any| Err(any) }; } -// TODO: fixed size map -// Note that each key is size_of([TypeId; 2]) = 32 bytes. -// Maybe risk type collisions and reduce key size. +/// The type-trait registry #[cfg(not(feature = "std"))] -type RegistryMap = heapless::FnvIndexMap<[TypeId; 2], &'static (dyn Any + Send + Sync), { 1 << 7 }>; +#[derive(Default, Debug, Clone)] +pub struct Registry<'a>( + // TODO: fixed size map + // Note that each key is size_of([TypeId; 2]) = 32 bytes. + // Maybe risk type collisions and reduce key size. + heapless::FnvIndexMap<[TypeId; 2], &'a (dyn Any + Send + Sync), { 1 << 7 }>, +); +/// The type-trait registry #[cfg(feature = "std")] -type RegistryMap = std::collections::HashMap<[TypeId; 2], &'static (dyn Any + Send + Sync)>; - -/// The type-trait registry. #[derive(Default, Debug, Clone)] -pub struct Registry(pub RegistryMap); +pub struct Registry<'a>(std::collections::HashMap<[TypeId; 2], &'a (dyn Any + Send + Sync)>); -impl Registry { - /// Obtain a `Caster` given the TypeId for the concrete type and a target trait `T`. +impl<'a> Registry<'a> { + /// Obtain a `Caster` given the TypeId for the concrete type and a target trait `T` fn caster(&self, any: TypeId) -> Option<&Caster> { self.0.get(&[TypeId::of::(), any])?.downcast_ref() } - /// Whether the `Any` can be cast to the target trait. + /// Whether the `Any` can be cast to the target trait pub fn castable_ref(&self, any: &dyn Any) -> bool { self.0.contains_key(&[TypeId::of::(), any.type_id()]) } - /// Whether the concrete type can be case to the target trait. + /// Whether the concrete type can be case to the target trait pub fn castable(&self) -> bool { self.0.contains_key(&[TypeId::of::(), TypeId::of::()]) } - /// Cast to an immuatbly borrowed trait object. + /// Cast to an immuatbly borrowed trait object pub fn cast_ref<'b, T: ?Sized + 'static>(&self, any: &'b dyn Any) -> Option<&'b T> { (self.caster(any.type_id())?.ref_)(any) } - /// Cast to a mutably borrowed trait object. + /// Cast to a mutably borrowed trait object pub fn cast_mut<'b, T: ?Sized + 'static>(&self, any: &'b mut dyn Any) -> Option<&'b mut T> { (self.caster((*any).type_id())?.mut_)(any) } - /// Cast to a boxed trait object. + /// Cast to a boxed trait object #[cfg(feature = "alloc")] pub fn cast_box(&self, any: Box) -> Result, Box> { match self.caster((*any).type_id()) { @@ -164,7 +166,7 @@ impl Registry { } } - /// Cast to a ref counted trait object. + /// Cast to a ref counted trait object #[cfg(feature = "std")] pub fn cast_rc(&self, any: Rc) -> Result, Rc> { match self.caster((*any).type_id()) { @@ -173,7 +175,7 @@ impl Registry { } } - /// Cast to an atomically ref counted trait object. + /// Cast to an atomically ref counted trait object #[cfg(feature = "std")] pub fn cast_arc( &self, @@ -186,13 +188,19 @@ impl Registry { } } -/// The global type registry -pub static REGISTRY: Lazy = - Lazy::new(|| Registry(__REGISTRY.iter().map(|maker| maker()).collect())); +/// The global type-trait registry +pub static REGISTRY: Lazy = Lazy::new(|| { + Registry( + __REGISTRY + .iter() + .map(|(key, value)| (key(), *value)) + .collect(), + ) +}); -/// Capability to determine whether we can be cast to a given trait object. +/// Whether a `dyn Any` can be cast to a given trait object pub trait CastableRef { - /// Whether we can be cast to a given trait object. + /// Whether we can be cast to a given trait object fn castable(self) -> bool; } @@ -202,9 +210,9 @@ impl CastableRef for &dyn Any { } } -/// Whether `Any` trait objects of this concrete type can be cast to a given trait object. +/// Whether this concrete type can be cast to a given trait object pub trait Castable { - /// This type is castable to the given trait object. + /// Whether this type is castable to the given trait object fn castable() -> bool; } @@ -214,8 +222,11 @@ impl Castable for U { } } -/// Cast an `Any` trait object to another trait object. +/// Cast an `dyn Any` to another given trait object +/// +/// Uses the global type-trait registry. pub trait Cast { + /// Cast a `dyn Any` (reference or smart pointer) to a given trait object fn cast(self) -> Option; } @@ -231,7 +242,7 @@ impl<'a, T: ?Sized + 'static> Cast<&'a mut T> for &'a mut dyn Any { } } -#[cfg(feature = "std")] +#[cfg(feature = "alloc")] impl Cast> for Box { fn cast(self) -> Option> { REGISTRY.cast_box(self).ok() From a5624eb8c8c41fc19c629a2692ebfe6c7ace9abf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Thu, 2 May 2024 23:57:41 +0200 Subject: [PATCH 05/11] add embedded example --- crosstrait/Cargo.toml | 1 + crosstrait/README.md | 6 +- crosstrait/src/lib.rs | 9 +-- crosstrait/tests/embedded/.cargo/config.toml | 8 +++ crosstrait/tests/embedded/.gitignore | 1 + crosstrait/tests/embedded/Cargo.toml | 20 +++++++ crosstrait/tests/embedded/build.rs | 18 ++++++ crosstrait/tests/embedded/memory.x | 11 ++++ crosstrait/tests/embedded/src/main.rs | 60 ++++++++++++++++++++ 9 files changed, 127 insertions(+), 7 deletions(-) create mode 100644 crosstrait/tests/embedded/.cargo/config.toml create mode 100644 crosstrait/tests/embedded/.gitignore create mode 100644 crosstrait/tests/embedded/Cargo.toml create mode 100644 crosstrait/tests/embedded/build.rs create mode 100644 crosstrait/tests/embedded/memory.x create mode 100644 crosstrait/tests/embedded/src/main.rs diff --git a/crosstrait/Cargo.toml b/crosstrait/Cargo.toml index 4e963f5b..23815d2f 100644 --- a/crosstrait/Cargo.toml +++ b/crosstrait/Cargo.toml @@ -19,5 +19,6 @@ once_cell = { version = "1.19", default-features = false, features = ["critical- [features] std = ["alloc", "once_cell/std"] +used_linker = ["linkme/used_linker"] alloc = [] default = ["std"] diff --git a/crosstrait/README.md b/crosstrait/README.md index 6bd7a040..465a5848 100644 --- a/crosstrait/README.md +++ b/crosstrait/README.md @@ -25,7 +25,7 @@ use core::{fmt::{Debug, Formatter, Write}, ops::{AddAssign, SubAssign}}; // Add types and trait implementations in the default global registy // Implementation status is verified at compile time -register!{ i32 => dyn Debug } +register! { i32 => dyn Debug } // Registering foreign types and traits works fine // Serialization/deserialization of `dyn Any` is a major use case @@ -75,10 +75,10 @@ let a: Option<&(dyn Debug + Sync)> = any.cast(); assert!(a.is_none()); // Registration can happen anywhere in any order in any downstream crate -register!{ i32 => dyn AddAssign } +register! { i32 => dyn AddAssign } // If a type is not Send + Sync, it can't cast as Arc. `no_arc` accounts for that -register!{ Formatter => dyn Write, no_arc } +register! { Formatter => dyn Write, no_arc } ``` ## Related crates diff --git a/crosstrait/src/lib.rs b/crosstrait/src/lib.rs index b6542784..cf142ed9 100644 --- a/crosstrait/src/lib.rs +++ b/crosstrait/src/lib.rs @@ -1,4 +1,5 @@ #![cfg_attr(not(any(test, doctest, feature = "std")), no_std)] +#![cfg_attr(feature = "used_linker", feature(used_with_arg))] #![cfg_attr(feature = "std", doc = include_str!("../README.md"))] use core::any::{Any, TypeId}; @@ -48,13 +49,13 @@ pub type Entry = (fn() -> [TypeId; 2], &'static (dyn Any + Send + Sync)); /// Static slice of key maker fns and Caster trait objects #[doc(hidden)] #[linkme::distributed_slice] -pub static __REGISTRY: [Entry]; +pub static REGISTRY_KV: [Entry]; /// Register a type and target trait in the registry #[macro_export] macro_rules! register { ( $ty:ty => $tr:ty $(, $flag:ident)? ) => { - $crate::register! { $crate::__REGISTRY: $ty => $tr $(, $flag)? } + $crate::register! { $crate::REGISTRY_KV: $ty => $tr $(, $flag)? } }; ( $reg:path: $ty:ty => $tr:ty $(, $flag:ident)? ) => { $crate::gensym! { $crate::register!{ $reg: $ty => $tr $(, $flag)? } } @@ -120,7 +121,7 @@ macro_rules! caster { #[cfg(not(feature = "std"))] #[derive(Default, Debug, Clone)] pub struct Registry<'a>( - // TODO: fixed size map + // TODO: fixed size map, ~6kB RAM // Note that each key is size_of([TypeId; 2]) = 32 bytes. // Maybe risk type collisions and reduce key size. heapless::FnvIndexMap<[TypeId; 2], &'a (dyn Any + Send + Sync), { 1 << 7 }>, @@ -191,7 +192,7 @@ impl<'a> Registry<'a> { /// The global type-trait registry pub static REGISTRY: Lazy = Lazy::new(|| { Registry( - __REGISTRY + REGISTRY_KV .iter() .map(|(key, value)| (key(), *value)) .collect(), diff --git a/crosstrait/tests/embedded/.cargo/config.toml b/crosstrait/tests/embedded/.cargo/config.toml new file mode 100644 index 00000000..a5893ae4 --- /dev/null +++ b/crosstrait/tests/embedded/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.thumbv7m-none-eabi] +runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel" + +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +rustflags = ["-C", "link-arg=-Tlink.x"] + +[build] +target = "thumbv7m-none-eabi" diff --git a/crosstrait/tests/embedded/.gitignore b/crosstrait/tests/embedded/.gitignore new file mode 100644 index 00000000..5a44eef0 --- /dev/null +++ b/crosstrait/tests/embedded/.gitignore @@ -0,0 +1 @@ +/Cargo.lock diff --git a/crosstrait/tests/embedded/Cargo.toml b/crosstrait/tests/embedded/Cargo.toml new file mode 100644 index 00000000..1bbcf5c6 --- /dev/null +++ b/crosstrait/tests/embedded/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "crosstrait-embedded-test" +version = "0.0.0" +edition = "2021" +publish = false + +[dependencies] +cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m-rt = "0.7" +cortex-m-semihosting = "0.5" +panic-semihosting = { version = "0.6", features = ["exit"] } + +crosstrait = { path = "../..", default-features = false } +miniconf = { path = "../../../miniconf", features = ["json-core", "postcard", "derive"] } + +[features] +used_linker = ["crosstrait/used_linker"] +default = ["used_linker"] + +[workspace] diff --git a/crosstrait/tests/embedded/build.rs b/crosstrait/tests/embedded/build.rs new file mode 100644 index 00000000..98f603ed --- /dev/null +++ b/crosstrait/tests/embedded/build.rs @@ -0,0 +1,18 @@ +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put the linker script somewhere the linker can find it + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // Only re-run the build script when memory.x is changed, + // instead of when any part of the source code changes. + println!("cargo:rerun-if-changed=memory.x"); +} diff --git a/crosstrait/tests/embedded/memory.x b/crosstrait/tests/embedded/memory.x new file mode 100644 index 00000000..c803f042 --- /dev/null +++ b/crosstrait/tests/embedded/memory.x @@ -0,0 +1,11 @@ +MEMORY +{ + FLASH : ORIGIN = 0x00000000, LENGTH = 256K + RAM : ORIGIN = 0x20000000, LENGTH = 64K +} + +SECTIONS { + linkme_REGISTRY_KV : { *(linkme_REGISTRY_KV) } > FLASH + linkm2_REGISTRY_KV : { *(linkm2_REGISTRY_KV) } > FLASH +} +INSERT AFTER .rodata diff --git a/crosstrait/tests/embedded/src/main.rs b/crosstrait/tests/embedded/src/main.rs new file mode 100644 index 00000000..fd4a69ae --- /dev/null +++ b/crosstrait/tests/embedded/src/main.rs @@ -0,0 +1,60 @@ +#![no_main] +#![no_std] +#![cfg_attr(feature = "used_linker", feature(used_with_arg))] +extern crate panic_semihosting; + +use core::any::Any; + +use cortex_m_rt::entry; +use cortex_m_semihosting::{debug, hprintln}; + +use crosstrait::{register, Cast}; +use miniconf::{self, Tree, TreeAny}; + +use core::ops::{AddAssign, SubAssign}; +register! { i32 => dyn AddAssign } +register! { u32 => dyn SubAssign } + +#[entry] +fn main() -> ! { + assert_eq!(crosstrait::REGISTRY_KV.len(), 2); + hprintln!( + "registry RAM: {}", + core::mem::size_of_val(&crosstrait::REGISTRY) + ); + + let mut a = 3i32; + let v: &mut dyn AddAssign = (&mut a as &mut dyn Any).cast().unwrap(); + *v += 5; + assert_eq!(a, 3 + 5); + + #[derive(Default, Tree)] + struct Inner { + val: i32, + } + + #[derive(Default, Tree)] + struct Settings { + #[tree(depth = 1)] + a: [i32; 2], + #[tree(depth = 2)] + i: [Inner; 3], + #[tree(depth = 1)] + b: Option, + } + let mut s = Settings::default(); + s.i[1].val = 3; + let key = miniconf::Packed::new_from_lsb(0b1_01_01_0).unwrap(); + // let key = miniconf::JsonPath::from(".i[1].val"); + let any = s.mut_any_by_key(key).unwrap(); + let val: &mut dyn AddAssign = any.cast().unwrap(); + *val += 5; + assert_eq!(s.i[1].val, 3 + 5); + + hprintln!("success!"); + + // exit QEMU + debug::exit(debug::EXIT_SUCCESS); + + loop {} +} From 873f4516f3b5b04e4b24244380c113f74249d1e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Fri, 3 May 2024 00:04:20 +0200 Subject: [PATCH 06/11] remove example picked up by crosstrait test --- miniconf/Cargo.toml | 6 ------ miniconf/examples/reflect.rs | 35 ----------------------------------- 2 files changed, 41 deletions(-) delete mode 100644 miniconf/examples/reflect.rs diff --git a/miniconf/Cargo.toml b/miniconf/Cargo.toml index 87818efb..c67fc91a 100644 --- a/miniconf/Cargo.toml +++ b/miniconf/Cargo.toml @@ -31,8 +31,6 @@ all-features = true [dev-dependencies] postcard = { version = "1.0.8", features = ["use-std"] } serde = { version = "1.0.120", features = ["derive"] } -crosstrait = { version = "0.1", path = "../crosstrait", default-features = false } -erased-serde = "0.4.4" [[test]] name = "arrays" @@ -73,7 +71,3 @@ required-features = ["json-core", "derive"] [[test]] name = "validate" required-features = ["json-core", "derive"] - -[[example]] -name = "reflect" -required-features = ["derive"] diff --git a/miniconf/examples/reflect.rs b/miniconf/examples/reflect.rs deleted file mode 100644 index 83df1f9e..00000000 --- a/miniconf/examples/reflect.rs +++ /dev/null @@ -1,35 +0,0 @@ -use core::fmt::Debug; -use core::ops::AddAssign; -use crosstrait::{register, Cast}; - -use miniconf::{JsonPath, TreeAny, TreeKey}; - -register! { u8 => dyn Debug } -register! { i32 => dyn AddAssign + Sync } -register! { i32 => dyn erased_serde::Serialize } - -#[derive(TreeKey, TreeAny, Default)] -struct Inner { - a: u8, -} - -#[derive(TreeKey, TreeAny, Default)] -struct Settings { - v: i32, - #[tree(depth = 2)] - i: [Inner; 2], -} - -fn main() { - let mut s = Settings::default(); - s.i[1].a = 9; - - let key: JsonPath = ".i[1].a".into(); - let a: &dyn Debug = s.ref_any_by_key(key).unwrap().cast().unwrap(); - println!("{a:?}"); - - let key: JsonPath = ".v".into(); - let v: &mut (dyn AddAssign + Sync) = s.mut_any_by_key(key).unwrap().cast().unwrap(); - *v += 3; - assert_eq!(s.v, 3); -} From baab451df649a40ebad6d9729bdb312d1e7e9d1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Fri, 3 May 2024 00:06:10 +0200 Subject: [PATCH 07/11] fix version --- miniconf_derive/Cargo.toml | 2 +- miniconf_mqtt/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/miniconf_derive/Cargo.toml b/miniconf_derive/Cargo.toml index 89a41fc4..8ba77496 100644 --- a/miniconf_derive/Cargo.toml +++ b/miniconf_derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "miniconf_derive" -version = "0.11.1" +version = "0.11.0" rust-version = "1.70.0" authors = ["James Irwin ", "Ryan Summers ", "Robert Jördens "] edition = "2021" diff --git a/miniconf_mqtt/Cargo.toml b/miniconf_mqtt/Cargo.toml index 4c0e63db..41d54cc2 100644 --- a/miniconf_mqtt/Cargo.toml +++ b/miniconf_mqtt/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "miniconf_mqtt" -version = "0.10.1" +version = "0.11.0" rust-version = "1.70.0" authors = ["James Irwin ", "Ryan Summers ", "Robert Jördens "] edition = "2021" From 7e4c4dff7c1f8372837bdaa6a6000f14685d8ea2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Fri, 3 May 2024 00:13:41 +0200 Subject: [PATCH 08/11] README: update --- miniconf/README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/miniconf/README.md b/miniconf/README.md index c86b381c..4fdd0d13 100644 --- a/miniconf/README.md +++ b/miniconf/README.md @@ -34,18 +34,18 @@ while `bevy_reflect` requires `'static` for `Reflect` types. * ➕ Iterate over struct fields: `miniconf` Supports recursive iteration over node keys. * ➕ Automatically serialize and deserialize via Serde without explicit serde impls: `miniconf` supports automatic serializing/deserializing into key-value pairs without an explicit container serde impls. -* (➕) Trait "reflection": `miniconf` has no integrated support but the `std` crate [`intertrait`](https://crates.io/crates/intertrait) - can be used to implement the type registry and cast from `dyn Any` returned by `TreeAny` to desired trait objects. - Together with `erased-serde` it can be used to implement node serialization/deserialization - using `miniconf`'s `TreeAny` without using `TreeSerialize`/`TreeDeserialize` similar to `bevy_reflect`, see the `reflect` example. - Another interesting crate is [`deflect`](https://crates.io/crates/deflect) - which allows reflection on trait objects (like `Any`) using adjacent DWARF debug info as the type registry. - It's `std` and experimental. +* ➕ Trait "reflection": Together with [`crosstrait`](https://crates.io/crates/crosstrait) supports building the + type registry and enables casting from `dyn Any` returned by `TreeAny` to desired trait objects. + Together with [`erased-serde`](https://crates.io/crates/erased-serde) it can be used to implement node serialization/deserialization + using `miniconf`'s `TreeAny` without using `TreeSerialize`/`TreeDeserialize` similar to `bevy_reflect`. Some tangential crates: -* [`serde-reflection`](https://crates.io/crates/serde-reflection): extract schemata +* [`serde-reflection`](https://crates.io/crates/serde-reflection): extract schemata from serde impls * [`typetag`](https://crates.io/crates/typetag): "derive serde for trait objects" (local traits and impls) +* [`deflect`](https://crates.io/crates/deflect): reflection on trait objects using adjacent DWARF debug info as the type registry +* [`intertrait`](https://crates.io/crates/intertrait): inspiration and source of ideas for `crosstrait` + ## Example From 83c0e65267be45523b83f6e9d50329f062df45eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Fri, 3 May 2024 00:29:32 +0200 Subject: [PATCH 09/11] don't test-compile for embedded, run emu --- .github/workflows/ci.yml | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a8fe6865..331f75f6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,7 +53,6 @@ jobs: with: profile: minimal toolchain: stable - target: thumbv7em-none-eabihf override: true - name: Cargo Doc @@ -77,8 +76,6 @@ jobs: toolchain: - stable - 1.70.0 # keep in sync with manifest MSRV - target: - - thumbv7em-none-eabihf steps: - uses: actions/checkout@v2 @@ -86,32 +83,30 @@ jobs: uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.toolchain }} - target: ${{ matrix.target }} override: true - name: Cargo Check uses: actions-rs/cargo@v1 with: command: check - args: --verbose --target ${{ matrix.target }} + args: --verbose - name: Cargo Build [No-Features] uses: actions-rs/cargo@v1 with: command: build - args: --no-default-features --target ${{ matrix.target }} + args: --no-default-features - name: Cargo Build uses: actions-rs/cargo@v1 with: command: build - args: --target ${{ matrix.target }} - name: Cargo Build [Release] uses: actions-rs/cargo@v1 with: command: build - args: --release --target ${{ matrix.target }} + args: --release - name: Cargo Build [Examples] uses: actions-rs/cargo@v1 @@ -153,6 +148,28 @@ jobs: command: test args: ${{ matrix.args }} + embedded: + runs-on: ubuntu-latest + timeout-minutes: 45 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly + with: + target: thumbv7m-none-eabi + - name: Install QEMU + run: | + sudo apt-get update + sudo apt-get install -y qemu-system-arm + - run: cargo run --release + env: + RUSTFLAGS: -C link-arg=-Tlink.x -D warnings + working-directory: crosstrait/tests/embedded + continue-on-error: true + - run: cargo run --release --features used_linker + env: + RUSTFLAGS: -C link-arg=-Tlink.x -D warnings + working-directory: crosstrait/tests/embedded + examples: runs-on: ubuntu-latest strategy: From 57ab566f408d58c6ae344100300e545a5ae3d9d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Fri, 3 May 2024 00:32:46 +0200 Subject: [PATCH 10/11] ci typo --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 331f75f6..6fa3df7d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -168,7 +168,7 @@ jobs: - run: cargo run --release --features used_linker env: RUSTFLAGS: -C link-arg=-Tlink.x -D warnings - working-directory: crosstrait/tests/embedded + working-directory: crosstrait/tests/embedded examples: runs-on: ubuntu-latest From 49618af5929a2b3237fa648d1de08aacc92e2614 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Fri, 3 May 2024 00:33:29 +0200 Subject: [PATCH 11/11] ci: test features fix --- .github/workflows/ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6fa3df7d..12c9533e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -123,9 +123,7 @@ jobs: - 1.70.0 # keep in sync with manifest MSRV args: - "" - - --all-features - --no-default-features - - --no-default-features --features json-core steps: - uses: actions/checkout@v2