Crate fruit_salad[][src]

Expand description

This is a (mostly) trait object reference casting and comparison crate.

Zulip Chat

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:

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

Object-safe Hash.

not that useful yet Object-safe Ord.

Reference downcasting, also to pinned trait objects.

not that useful yet Dyncast and dynamically Eq

not that useful yet DyncastEq and dynamically Ord

Derive Macros

Implements Dyncast for an enum, struct, trait, trait alias, type alias or union.
Requires feature "macros".