Crate fruit_salad[−][src]
Expand description
This is a (mostly) trait object reference casting and comparison crate.
There is no registry, instead targets are engraved directly into the Dyncast
trait implementation by a derive macro.
Concrete types can be targeted too, unsafely through reinterpret casts.
(This is subject to #[deny(unsafe)]
. (TODO))
It also does mutability and pin projection, while being economical regarding text size…
Basically I needed something that’s a bit less fancy than the existing solutions, and it escalated a bit from there.
Originally developed as part of rhizome
but now separate,
this crate also works very well in combination with pinus
.
WIP
Due to diminishing returns, I’ve currently left functionality related to complete comparisons unimplemented.
Relevant parts of the documentation are labelled not that useful yet
and stricken through where unimplemented.
Installation
Please use cargo-edit to always add the latest version of this library:
cargo add fruit-salad --features macros
Features
"alloc"
Requires the alloc
crate and enables casting Box<dyn Dyncast>
into other boxes.
"macros"
Makes the Dyncast
derive macro and implement_dyncasts!
macro available.
"std"
(default)
Requires the std
crate and implies "alloc"
.
Example
#[cfg(feature = "macros")]
{
#![allow(clippy::eq_op)] // Identical args are intentional.
use core::fmt::Debug;
use fruit_salad::Dyncast; // With feature `"macros"`.
#[derive(PartialEq, Dyncast, Hash)]
#[dyncast(Self, impl dyn DynHash)]
struct A;
#[derive(Debug, PartialEq, PartialOrd, Dyncast)]
#[dyncast(Self, dyn Debug)]
#[dyncast(impl dyn PartialEq<dyn Dyncast>, impl dyn PartialOrd<dyn Dyncast>)]
struct B;
let a: &dyn Dyncast = &A;
let b: &dyn Dyncast = &B;
assert_ne!(a, a); // Partial equality isn't exposed.
assert_eq!(b, b);
assert_ne!(a, b);
assert_ne!(b, a);
assert_eq!(a.partial_cmp(a), None); // Partial order isn't exposed.
assert_eq!(b.partial_cmp(b), Some(core::cmp::Ordering::Equal));
assert_eq!(a.partial_cmp(b), None);
assert_eq!(b.partial_cmp(a), None);
assert_eq!(format!("{:?}", a), "dyn Dyncast = !dyn Debug");
assert_eq!(format!("{:?}", b), "dyn Dyncast = B");
assert!(a.dyncast::<dyn Debug>().is_none());
assert!(b.dyncast::<dyn Debug>().is_some());
// Also: `…_mut`, `…_pinned`, `…_box` and combinations thereof, as well as `…ptr`.
// `…box` methods require the `"alloc"` feature.
let _a: &A = a.dyncast().unwrap();
let _b: &B = b.dyncast().unwrap();
}
☡ Potential Trip-ups
While containing a safe API that covers many use-cases, this is still a runtime casting crate. Meaning that whether a cast will succeed can’t be checked at compile-time for the most part.
When you derive Dyncast
, you really need to announce each dyncast target type with the #[dyncast(Type)]
attribute.
It’s variadic, stacks and mostly allows repeats, so that’s quite flexible. However, repeat types currently aren’t explicitly deduplicated. The compiler may still do so, but keep it in mind.
The order of dyncast targets may also affect performance, since the targets are checked against in sequence.
You can use Self
as shorthand for the current type, which also works with generics,
but limits the generated Dyncast
implementation by where Self: 'static
.
Announced dyncast targets must all be
'static
, but the concrete instance doesn’t have to be.
Dynamic formatting
Values will be formatted as dyn Dyncast = !dyn Debug
or dyn Dyncast = !dyn Display
if they are not dynamically Debug
or not dynamically Display
, respectively.
Add #[dyncast(dyn Debug)]
and/or #[dyncast(dyn Display)]
where you derive or implement this trait.
Partial comparisons
dyn Dyncast
trait object comparisons through PartialEq
and PartialOrd
will always return false
or None
(respectively)
unless at least one of the underlying types is dynamically PartialEq<dyn Dyncast>
or dynamically PartialOrd<dyn Dyncast>
, respectively.
These implementations should mirror each other if both are available.
You can write #[dyncast(impl dyn PartialEq<dyn Dyncast>)]
and #[dyncast(impl dyn PartialOrd<dyn Dyncast>)]
to
generate implementations for these, respectively based on the plain PartialEq
and PartialOrd
implementations.
Comparisons between distinct types will always result in false
or None
with the generated implementations.
not that useful yet
Complete comparisons
Dyncast
alone never exposes complete comparisons without explicit dyncast.
However, the following subtraits are available for more completely comparable values:
DyncastEq
, which is alsoDeref<Target = dyn Dyncast>
, andDyncastOrd
, which is alsoDeref<Target = dyn DyncastEq>
.
Additionally, DynOrd
is an object-safe version of Ord
and can be generated,
conditional on .Self
being Ord
and Any
That’s simplified a bit, but close enough.
DyncastEq
and DyncastOrd
can both be implemented manually.
A DyncastEq
implementation is generated implicitly iff you write #[dyncast(impl dyn PartialEq<dyn Dyncast>)]
,
conditional on Self
being Eq
.
A DyncastOrd
implementation is generated implicitly iff you write #[dyncast(impl dyn PartialOrd<dyn Dyncast>, impl dyn DynOrd)]
,
conditional on Self
being DyncastEq
, Ord
and Any
.
Hashing
Meaningful hashing requires that the underlying type be dynamically DynHash
.
A blanket implementation is available for types that are Hash
,
but you still need to enable the dyncast using #[dyncast(dyn DynHash)]
.
Other types (that are not dynamically DynHash
) hash dynamically by not hashing anything.
For convenience, you can enable this dyncast without importing DynHash
by writing #[dyncast(impl dyn DynHash)]
.
Macros
Implements Dyncast
for an enum, struct, trait, trail alias, type alias or union.
Requires feature "macros"
.
Traits
Reference downcasting, also to pinned trait objects.
Derive Macros
Implements Dyncast
for an enum, struct, trait, trait alias, type alias or union.
Requires feature "macros"
.