fruit_salad/lib.rs
1//! This is a (mostly) trait object **reference** casting and comparison crate.
2//!
3//! [](https://iteration-square.schichler.dev/#narrow/stream/project.2Ffruit-salad)
4//!
5//! There is no registry, instead targets are engraved directly into the `Dyncast` trait implementation by a derive macro.
6//!
7//! Concrete types can be targeted too, unsafely through reinterpret casts.
8//! (This is subject to `#[deny(unsafe)]`. (TODO))
9//!
10//! It also does mutability and pin projection, while being economical regarding text size…
11//!
12//! > Basically I needed something that's a bit less fancy than the existing solutions,
13//! > and it escalated a bit from there.
14//!
15//! Originally developed as part of [`rhizome`](https://crates.io/crates/rhizome) but now separate,
16//! this crate also works very well in combination with [`pinus`](https://crates.io/crates/pinus).
17//!
18//! # WIP
19//!
20//! Due to diminishing returns,
21//! I've currently left functionality related to complete comparisons unimplemented.
22//!
23//! Relevant parts of the documentation are labelled `not that useful yet` and ~~stricken through~~ where unimplemented.
24//!
25//! # Installation
26//!
27//! Please use [cargo-edit](https://crates.io/crates/cargo-edit) to always add the latest version of this library:
28//!
29//! ```cmd
30//! cargo add fruit-salad --features macros
31//! ```
32//!
33//! # Features
34//!
35//! ## `"alloc"`
36//!
37//! Requires the [`alloc`] crate and enables casting [`Box<dyn Dyncast>`](`alloc::boxed::Box`) into other boxes.
38//!
39//! ## `"macros"`
40//!
41//! Makes the [`Dyncast`](derive.Dyncast.html) derive macro and [`implement_dyncasts!`](macro.implement_dyncasts.html) macro available.
42//!
43//! ## `"std"` (default)
44//!
45//! Requires the [`std`] crate and implies `"alloc"`.
46//!
47//! # Example
48//!```rust
49//! #[cfg(feature = "macros")]
50//! {
51//! #![allow(clippy::eq_op)] // Identical args are intentional.
52//!
53//! use core::fmt::Debug;
54//! use fruit_salad::Dyncast; // With feature `"macros"`.
55//!
56//! #[derive(PartialEq, Dyncast, Hash)]
57//! #[dyncast(Self, impl dyn DynHash)]
58//! struct A;
59//!
60//! #[derive(Debug, PartialEq, PartialOrd, Dyncast)]
61//! #[dyncast(Self, dyn Debug)]
62//! #[dyncast(impl dyn PartialEq<dyn Dyncast>, impl dyn PartialOrd<dyn Dyncast>)]
63//! struct B;
64//!
65//! let a: &dyn Dyncast = &A;
66//! let b: &dyn Dyncast = &B;
67//!
68//! assert_ne!(a, a); // Partial equality isn't exposed.
69//! assert_eq!(b, b);
70//! assert_ne!(a, b);
71//! assert_ne!(b, a);
72//!
73//! assert_eq!(a.partial_cmp(a), None); // Partial order isn't exposed.
74//! assert_eq!(b.partial_cmp(b), Some(core::cmp::Ordering::Equal));
75//! assert_eq!(a.partial_cmp(b), None);
76//! assert_eq!(b.partial_cmp(a), None);
77//!
78//! assert_eq!(format!("{:?}", a), "dyn Dyncast = !dyn Debug");
79//! assert_eq!(format!("{:?}", b), "dyn Dyncast = B");
80//!
81//! assert!(a.dyncast::<dyn Debug>().is_none());
82//! assert!(b.dyncast::<dyn Debug>().is_some());
83//!
84//! // Also: `…_mut`, `…_pinned`, `…_box` and combinations thereof, as well as `…ptr`.
85//! // `…box` methods require the `"alloc"` feature.
86//! let _a: &A = a.dyncast().unwrap();
87//! let _b: &B = b.dyncast().unwrap();
88//! }
89//! ```
90//!
91//! # ☡ Potential Trip-ups
92//!
93//! While containing a safe API that covers many use-cases, this is still a *runtime* casting crate.
94//! Meaning that whether a cast will succeed can't be checked at compile-time for the most part.
95//!
96//! When you derive [`Dyncast`], you really need to announce each dyncast target type with the `#[dyncast(Type)]` attribute.
97//!
98//! > It's variadic, stacks and mostly allows repeats, so that's quite flexible.
99//! > However, repeat types currently aren't explicitly deduplicated.
100//! > The compiler *may* still do so, but keep it in mind.
101//! >
102//! > The order of dyncast targets may also affect performance,
103//! > since the targets are checked against in sequence.
104//!
105//! You can use `Self` as shorthand for the current type, which also works with generics,
106//! but limits the generated [`Dyncast`] implementation by `where Self: 'static`.
107//!
108//! > Announced dyncast targets must all be `'static`, but the concrete instance doesn't have to be.
109//!
110//! ## Dynamic formatting
111//!
112//! Values will be formatted as `dyn Dyncast = !dyn Debug` or `dyn Dyncast = !dyn Display`
113//! if they are not *dynamically* [`Debug`] or not *dynamically* [`Display`], respectively.
114//!
115//! Add `#[dyncast(dyn Debug)]` and/or `#[dyncast(dyn Display)]` where you derive or implement this trait.
116//!
117//! ## Partial comparisons
118//!
119//! [`dyn Dyncast`](`Dyncast`) trait object comparisons through [`PartialEq`] and [`PartialOrd`] will always return [`false`] or [`None`] (respectively)
120//! unless at least one of the underlying types is *dynamically* [`PartialEq<dyn Dyncast>`] or *dynamically* [`PartialOrd<dyn Dyncast>`], respectively.
121//!
122//! > These implementations should mirror each other if both are available.
123//!
124//! You can write `#[dyncast(impl dyn PartialEq<dyn Dyncast>)]` and `#[dyncast(impl dyn PartialOrd<dyn Dyncast>)]` to
125//! generate implementations for these, respectively based on the plain [`PartialEq`] and [`PartialOrd`] implementations.
126//!
127//! Comparisons between distinct types will always result in [`false`] or [`None`] with the generated implementations.
128//!
129//! ## `not that useful yet` Complete comparisons
130//!
131//! [`Dyncast`] alone never exposes complete comparisons without explicit dyncast.
132//!
133//! However, the following subtraits are available for more completely comparable values:
134//!
135//! - [`DyncastEq`], which is also [`Deref<Target = dyn Dyncast>`](`Deref`), and
136//! - [`DyncastOrd`], which is also [`Deref<Target = dyn DyncastEq>`](`Deref`).
137//!
138//! Additionally, [`DynOrd`] is an object-safe version of [`Ord`] <s>and can be generated,
139//! conditional on `Self` being [`Ord`] and [`Any`](`core::any::Any`)</s>.
140//!
141//! > That's simplified a bit, but close enough.
142//!
143//! [`DyncastEq`] and [`DyncastOrd`] can both be implemented manually.
144//!
145//! <s>
146//!
147//! A [`DyncastEq`] implementation is generated implicitly iff you write `#[dyncast(impl dyn PartialEq<dyn Dyncast>)]`,
148//! conditional on `Self` being [`Eq`].
149//!
150//! A [`DyncastOrd`] implementation is generated implicitly iff you write `#[dyncast(impl dyn PartialOrd<dyn Dyncast>, impl dyn DynOrd)]`,
151//! conditional on `Self` being [`DyncastEq`], [`Ord`] and [`Any`](`core::any::Any`).
152//!
153//! </s>
154//!
155//! ## Hashing
156//!
157//! Meaningful hashing requires that the underlying type be *dynamically* [`DynHash`].
158//!
159//! A blanket implementation is available for types that are [`Hash`],
160//! but you still need to enable the dyncast using `#[dyncast(dyn DynHash)]`.
161//!
162//! Other types (that are not dynamically [`DynHash`]) hash dynamically by not hashing anything.
163//!
164//! <!-- FIXME: It would be good to emit a warning for types that are hash but not dynamically DynHash, where possible. -->
165//!
166//! For convenience, you can enable this dyncast without importing [`DynHash`] by writing `#[dyncast(impl dyn DynHash)]`.
167
168#![doc(html_root_url = "https://docs.rs/fruit-salad/0.0.2")]
169#![warn(clippy::pedantic, missing_docs)]
170#![allow(clippy::semicolon_if_nothing_returned)]
171#![no_std]
172
173#[cfg(feature = "alloc")]
174extern crate alloc;
175
176#[cfg(feature = "std")]
177extern crate std;
178
179#[cfg(doctest)]
180pub mod readme {
181 doc_comment::doctest!("../README.md");
182}
183
184use core::{
185 any::TypeId,
186 cmp::Ordering,
187 fmt::{self, Debug, Display},
188 hash::{Hash, Hasher},
189 mem::{self, MaybeUninit},
190 ops::{Deref, DerefMut},
191 pin::Pin,
192 ptr::NonNull,
193};
194
195#[cfg(feature = "macros")]
196pub use fruit_salad_proc_macro_definitions::{implement_dyncasts, Dyncast};
197
198/// Limited-lifetime API. This could be safe in one or two ways:
199///
200/// 1. Grabbing the [`TypeId`] of non-`'static` types.
201///
202/// > This is impossible by design and I mostly agree with that.
203/// > It would probably encourage many unsound hacks.
204///
205/// 2. A way to statically derive a shortened object (not just reference!) lifetime from a `'static` one.
206///
207/// > This would solve the issue very nicely, but would definitely require at least a very magic core library function.
208/// > I hope it will eventually come to the language, nonetheless.
209///
210/// If either of these possibilities landed, the safe API's coverage could be expanded to (most of) this unsafe one's use cases,
211/// and this unsafe API's methods could be deprecated in bulk.
212impl<'a> dyn 'a + Dyncast {
213 /// # Safety
214 ///
215 /// `TActual` and `TStatic` must be the same type except for lifetimes.
216 ///
217 /// `TActual` must not be longer-lived than `Self`.
218 #[allow(missing_docs)]
219 #[must_use]
220 pub unsafe fn dyncast_<TActual: ?Sized, TStatic: 'static + ?Sized>(&self) -> Option<&TActual> {
221 self.__dyncast(
222 NonNull::new_unchecked(self as *const Self as *mut Self).cast(),
223 TypeId::of::<TStatic>(),
224 )
225 .map(|pointer_data| {
226 #[allow(clippy::cast_ptr_alignment)] // Read unaligned.
227 pointer_data.as_ptr().cast::<&TActual>().read_unaligned()
228 })
229 }
230
231 /// # Safety
232 ///
233 /// `TActual` and `TStatic` must be the same type except for lifetimes.
234 ///
235 /// `TActual` must not be longer-lived than `Self`.
236 #[allow(missing_docs)]
237 #[must_use]
238 pub unsafe fn dyncast_mut_<TActual: ?Sized, TStatic: 'static + ?Sized>(
239 &mut self,
240 ) -> Option<&mut TActual> {
241 let this = NonNull::new_unchecked(self);
242 self.__dyncast(this.cast(), TypeId::of::<TStatic>())
243 .map(|pointer_data| {
244 pointer_data
245 .as_ptr()
246 .cast::<&mut TActual>()
247 .read_unaligned()
248 })
249 }
250
251 /// # Safety
252 ///
253 /// `TActual` and `TStatic` must be the same type except for lifetimes.
254 ///
255 /// `TActual` must not be longer-lived than `Self`.
256 #[allow(missing_docs)]
257 #[must_use]
258 pub unsafe fn dyncast_pinned_<TActual: ?Sized, TStatic: 'static + ?Sized>(
259 self: Pin<&Self>,
260 ) -> Option<Pin<&TActual>> {
261 self.__dyncast(
262 NonNull::new_unchecked(&*self as *const Self as *mut Self).cast(),
263 TypeId::of::<TStatic>(),
264 )
265 .map(|pointer_data| {
266 pointer_data
267 .as_ptr()
268 .cast::<Pin<&TActual>>()
269 .read_unaligned()
270 })
271 }
272
273 /// # Safety
274 ///
275 /// `TActual` and `TStatic` must be the same type except for lifetimes.
276 ///
277 /// `TActual` must not be longer-lived than `Self`.
278 #[allow(missing_docs)]
279 #[must_use]
280 pub unsafe fn dyncast_pinned_mut_<TActual: ?Sized, TStatic: 'static + ?Sized>(
281 mut self: Pin<&mut Self>,
282 ) -> Option<Pin<&mut TActual>> {
283 let this = NonNull::new_unchecked(Pin::into_inner_unchecked(self.as_mut()) as *mut Self);
284 self.__dyncast(this.cast(), TypeId::of::<TStatic>())
285 .map(|pointer_data| {
286 pointer_data
287 .as_ptr()
288 .cast::<Pin<&mut TActual>>()
289 .read_unaligned()
290 })
291 }
292
293 /// # Safety
294 ///
295 /// See [`NonNull::as_ref`].
296 ///
297 /// Additionally, `TActual` and `TStatic` must be the same type except for lifetimes.
298 ///
299 /// `TActual` must not be longer-lived than `Self`.
300 #[allow(missing_docs)]
301 #[must_use]
302 pub unsafe fn dyncast_ptr_<TActual: ?Sized, TStatic: 'static + ?Sized>(
303 this: NonNull<Self>,
304 ) -> Option<NonNull<TActual>> {
305 this.as_ref()
306 .__dyncast(this.cast(), TypeId::of::<TStatic>())
307 .map(|pointer_data| {
308 pointer_data
309 .as_ptr()
310 .cast::<NonNull<TActual>>()
311 .read_unaligned()
312 })
313 }
314
315 /// Requires feature `"alloc"`.
316 ///
317 /// # Safety
318 ///
319 /// `TActual` and `TStatic` must be the same type except for lifetimes.
320 ///
321 /// `TActual` must not be longer-lived than `Self`.
322 ///
323 /// # Errors
324 ///
325 /// Iff the cast fails, the original [`Box`](`alloc::boxed::Box`) is restored.
326 #[allow(missing_docs)]
327 #[cfg(feature = "alloc")]
328 pub unsafe fn dyncast_box_<TActual: ?Sized, TStatic: 'static + ?Sized>(
329 self: alloc::boxed::Box<Self>,
330 ) -> Result<alloc::boxed::Box<TActual>, alloc::boxed::Box<Self>> {
331 use alloc::boxed::Box;
332
333 let leaked = Box::into_raw(self);
334
335 (&*leaked)
336 .__dyncast(
337 NonNull::new_unchecked(leaked).cast(),
338 TypeId::of::<TStatic>(),
339 )
340 .map(|pointer_data| {
341 #[allow(clippy::cast_ptr_alignment)] // Read unaligned.
342 Box::from_raw(
343 pointer_data
344 .as_ptr()
345 .cast::<*mut TActual>()
346 .read_unaligned(),
347 )
348 })
349 .ok_or_else(|| Box::from_raw(leaked))
350
351 // Normally there should be a bit of error handling here to prevent leaks in cases where `.__dyncast` panics,
352 // but since this crate fully controls that implementation, we can assume that just never happens in a meaningful way.
353 //
354 // There might still be panics if the caller violates a safety contract somehow, but in that case all bets are off anyway.
355 }
356
357 /// Requires feature `"alloc"`.
358 ///
359 /// # Safety
360 ///
361 /// `TActual` and `TStatic` must be the same type except for lifetimes.
362 ///
363 /// `TActual` must not be longer-lived than `Self`.
364 ///
365 /// # Errors
366 ///
367 /// Iff the cast fails, the original [`Box`](`alloc::boxed::Box`) is restored.
368 #[allow(missing_docs)]
369 #[cfg(feature = "alloc")]
370 pub unsafe fn dyncast_pinned_box_<TActual: ?Sized, TStatic: 'static + ?Sized>(
371 self: Pin<alloc::boxed::Box<Self>>,
372 ) -> Result<Pin<alloc::boxed::Box<TActual>>, Pin<alloc::boxed::Box<Self>>> {
373 use alloc::boxed::Box;
374
375 let leaked = Box::into_raw(Pin::into_inner_unchecked(self));
376
377 (&*leaked)
378 .__dyncast(
379 NonNull::new_unchecked(leaked).cast(),
380 TypeId::of::<TStatic>(),
381 )
382 .map(|pointer_data| {
383 #[allow(clippy::cast_ptr_alignment)] // Read unaligned.
384 Pin::new_unchecked(Box::from_raw(
385 pointer_data
386 .as_ptr()
387 .cast::<*mut TActual>()
388 .read_unaligned(),
389 ))
390 })
391 .ok_or_else(|| Pin::new_unchecked(Box::from_raw(leaked)))
392
393 // Normally there should be a bit of error handling here to prevent leaks in cases where `.__dyncast` panics,
394 // but since this crate fully controls that implementation, we can assume that just never happens in a meaningful way.
395 //
396 // There might still be panics if the caller violates a safety contract somehow, but in that case all bets are off anyway.
397 }
398}
399
400/// Safe `'static`-object dyncast API.
401///
402/// This can't be misused, but it will only work if the targeted instances are owned/`dyn 'static + Dyncast`.
403///
404/// > It does work on short-lived references to such trait objects, though.
405impl dyn Dyncast {
406 #[allow(missing_docs)]
407 #[must_use]
408 pub fn dyncast<T: 'static + ?Sized>(&self) -> Option<&T> {
409 unsafe { self.dyncast_::<T, T>() }
410 }
411
412 #[allow(missing_docs)]
413 #[must_use]
414 pub fn dyncast_mut<T: 'static + ?Sized>(&mut self) -> Option<&mut T> {
415 unsafe { self.dyncast_mut_::<T, T>() }
416 }
417
418 #[allow(missing_docs)]
419 #[must_use]
420 pub fn dyncast_pinned<T: 'static + ?Sized>(self: Pin<&Self>) -> Option<Pin<&T>> {
421 unsafe { self.dyncast_pinned_::<T, T>() }
422 }
423
424 #[allow(missing_docs)]
425 #[must_use]
426 pub fn dyncast_pinned_mut<T: 'static + ?Sized>(self: Pin<&mut Self>) -> Option<Pin<&mut T>> {
427 unsafe { self.dyncast_pinned_mut_::<T, T>() }
428 }
429
430 /// # Safety
431 ///
432 /// See [`NonNull::as_ref`].
433 #[allow(missing_docs)]
434 #[must_use]
435 pub unsafe fn dyncast_ptr<T: 'static + ?Sized>(this: NonNull<Self>) -> Option<NonNull<T>> {
436 Self::dyncast_ptr_::<T, T>(this)
437 }
438
439 /// Requires feature `"alloc"`.
440 ///
441 /// # Errors
442 ///
443 /// Iff the cast fails, the original [`Box`](`alloc::boxed::Box`) is restored.
444 #[allow(missing_docs)]
445 #[cfg(feature = "alloc")]
446 pub fn dyncast_box<T: 'static + ?Sized>(
447 self: alloc::boxed::Box<Self>,
448 ) -> Result<alloc::boxed::Box<T>, alloc::boxed::Box<Self>> {
449 unsafe { self.dyncast_box_::<T, T>() }
450 }
451
452 /// Requires feature `"alloc"`.
453 ///
454 /// # Errors
455 ///
456 /// Iff the cast fails, the original [`Box`](`alloc::boxed::Box`) is restored.
457 #[allow(missing_docs)]
458 #[cfg(feature = "alloc")]
459 pub fn dyncast_pinned_box<T: 'static + ?Sized>(
460 self: Pin<alloc::boxed::Box<Self>>,
461 ) -> Result<Pin<alloc::boxed::Box<T>>, Pin<alloc::boxed::Box<Self>>> {
462 unsafe { self.dyncast_pinned_box_::<T, T>() }
463 }
464}
465
466/// Reference downcasting, also to pinned trait objects.
467///
468/// # ☡ Cognitohazard Warning ☡
469///
470/// There is Generally Not Neat code here.
471///
472/// Use the [`Dyncast`](./derive.Dyncast.html) derive macro to implement this and don't worry about it too much.
473///
474/// I've put in some const assertions and don't rely on unstable behaviour,
475/// so this shouldn't fail silently, at least.
476///
477/// > **Wishlist**
478/// >
479/// > - `self: NonNull<Self>` as receiver in object-safe `unsafe` trait methods.
480/// > - [#81513](https://github.com/rust-lang/rust/issues/81513) or similar.
481pub unsafe trait Dyncast {
482 /// This likely warrants a bit of an explanation,
483 /// even though it's really not part of the public API,
484 /// and the main purpose of this crate is to *stop* API consumers from
485 /// wasting their time with navigating this mess on their end/in their code.
486 ///
487 /// I'll put in here for the curious, everyone else will just see the trait docs only.
488 #[doc(hidden)]
489 ///
490 /// In short, what you're looking at is a sort of focal point of some shortcomings
491 /// that Rust currently has with regards to dynamic dispatch, as there are many
492 /// limitations to object-safety that aren't necessarily technical in nature.
493 ///
494 /// (I'm not saying they really NEED to be fixed with a high priority though,
495 /// since for the most part each workaround is simple enough by itself and
496 /// shouldn't produce all too much overhead.)
497 ///
498 /// First off, this method uses pointers because each of the six variations
499 /// of reference used (shared, mutable, pinned shared, pinned mutable,
500 /// [`NonNull`] and [`Box`](`alloc::boxed::Box`)) requires the exact same operations.
501 /// By only using this one method, there may be a significant reduction in text size if
502 /// many concrete types are dyncast-enabled.
503 ///
504 /// > [`Box`](`alloc::boxed::Box`)es aren't punned directly of course,
505 /// but converted using their API functions in the relevant shim.
506 ///
507 /// In any case, pointers are unfortunately not valid receivers,
508 /// which is one reason there is a `this` parameters here.
509 ///
510 /// The other reason is that even object-safety is quite picky about `Self`
511 /// appearing in arguments. `this` must be the raw address `NonNull<()>`
512 /// due to that limitation. Hopefully both will change in time.
513 ///
514 /// The `target` parameter is provided as `TypeId` because a generic
515 /// type argument would make the method not object-safe.
516 ///
517 /// Similarly, it's unknown which memory layout the resulting pointer will have.
518 /// The size of `&dyn Dyncast` is assumed to be enough to store it, but this is also
519 /// validated statically in the generated implementation(s) of this method.
520 ///
521 /// The return memory (interpreted as pointer) is also considered to be unaligned for its contents
522 /// (while the eventual pointee instance must be aligned), so unaligned writes and reads are used for it.
523 /// There may be a better method to do this. If you know one,
524 /// please don't hesitate to contact me/the project about it!
525 ///
526 /// # Implementation
527 ///
528 /// This method's implementations are generated is as long if-else chains, each
529 /// with one branch for each dyncast target plus one default branch returning [`None`].
530 ///
531 /// The [`TypeId`] given as `target` is compared with ones baked into the function,
532 /// and if there's a match, the pointer is converted to the target type (widening it if
533 /// necessary) and then returned not-further-modified within the allotted memory.
534 #[allow(clippy::type_complexity)]
535 fn __dyncast(
536 &self,
537 this: NonNull<()>,
538 target: TypeId,
539 ) -> Option<MaybeUninit<[u8; mem::size_of::<&dyn Dyncast>()]>>;
540}
541
542impl<'a> Debug for dyn 'a + Dyncast {
543 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
544 f.write_str("dyn Dyncast = ")?;
545 #[allow(clippy::option_if_let_else)] // Can't because `f`.
546 if let Some(debug) = unsafe { self.dyncast_::<dyn 'a + Debug, dyn Debug>() } {
547 debug.fmt(f)
548 } else {
549 f.write_str("!dyn Debug")
550 }
551 }
552}
553
554impl<'a> Display for dyn 'a + Dyncast {
555 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
556 #[allow(clippy::option_if_let_else)] // Can't because `f`.
557 if let Some(display) = unsafe { self.dyncast_::<dyn 'a + Display, dyn Display>() } {
558 display.fmt(f)
559 } else {
560 f.write_str("dyn Dyncast = !dyn Display")
561 }
562 }
563}
564
565impl<'a> PartialEq for dyn 'a + Dyncast {
566 fn eq(&self, other: &Self) -> bool {
567 unsafe {
568 if let Some(this) =
569 Self::dyncast_::<dyn 'a + PartialEq<Self>, dyn PartialEq<dyn Dyncast>>(self)
570 {
571 this.eq(other)
572 } else if let Some(other) =
573 Self::dyncast_::<dyn 'a + PartialEq<Self>, dyn PartialEq<dyn Dyncast>>(other)
574 {
575 other.eq(self)
576 } else {
577 false
578 }
579 }
580 }
581}
582
583impl<'a> PartialOrd for dyn 'a + Dyncast {
584 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
585 unsafe {
586 if let Some(this) =
587 Self::dyncast_::<dyn 'a + PartialOrd<Self>, dyn PartialOrd<dyn Dyncast>>(self)
588 {
589 this.partial_cmp(other)
590 } else if let Some(other) =
591 Self::dyncast_::<dyn 'a + PartialOrd<Self>, dyn PartialOrd<dyn Dyncast>>(other)
592 {
593 other.partial_cmp(self).map(Ordering::reverse)
594 } else {
595 None
596 }
597 }
598 }
599}
600
601/// Object-safe [`Hash`].
602pub trait DynHash {
603 /// Hashes this instance.
604 /// See [`Hash::hash`].
605 fn dyn_hash(&self, state: &mut dyn Hasher);
606}
607impl<T: ?Sized> DynHash for T
608where
609 T: Hash,
610{
611 fn dyn_hash(&self, mut state: &mut dyn Hasher) {
612 <Self as Hash>::hash(self, &mut state)
613 }
614}
615impl Hash for dyn DynHash {
616 fn hash<H: Hasher>(&self, state: &mut H) {
617 self.dyn_hash(state)
618 }
619}
620
621impl<'a> Hash for dyn 'a + Dyncast {
622 fn hash<H: Hasher>(&self, state: &mut H) {
623 if let Some(dyn_hash) = unsafe { self.dyncast_::<dyn 'a + DynHash, dyn DynHash>() } {
624 dyn_hash.dyn_hash(state)
625 }
626 }
627}
628
629/// `not that useful yet` Object-safe [`Ord`].
630///
631/// Where possible, prefer [`DyncastOrd`] over manually [`dyncast`](trait.Dyncast.html#method.dyncast)ing to [`dyn DynOrd`].
632pub unsafe trait DynOrd {
633 /// Retrieves the [`TypeId`] of the underlying instance.
634 fn concrete_type_id(&self) -> TypeId;
635
636 /// Dynamically compares `self` to `other`.
637 fn dyn_cmp(&self, other: &dyn DynOrd) -> Ordering;
638}
639
640///TODO: This should be an explicitly generated implementation, instead.
641// unsafe impl<T> DynOrd for T
642// where
643// T: Ord + Any,
644// {
645// fn concrete_type_id(&self) -> TypeId {
646// self.type_id()
647// }
648
649// fn dyn_cmp(&self, other: &dyn DynOrd) -> Ordering {
650// match self.concrete_type_id().cmp(&other.concrete_type_id()) {
651// Ordering::Equal => self.cmp(unsafe { &*(other as *const dyn DynOrd).cast::<Self>() }),
652// not_equal => not_equal,
653// }
654// }
655// }
656impl<'a> PartialEq<dyn 'a + DynOrd> for dyn 'a + DynOrd {
657 fn eq(&self, other: &Self) -> bool {
658 self.dyn_cmp(other) == Ordering::Equal
659 }
660}
661impl<'a> PartialOrd<dyn 'a + DynOrd> for dyn 'a + DynOrd {
662 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
663 Some(self.dyn_cmp(other))
664 }
665}
666impl<'a> Eq for dyn 'a + DynOrd {}
667impl<'a> Ord for dyn 'a + DynOrd {
668 fn cmp(&self, other: &Self) -> Ordering {
669 self.dyn_cmp(other)
670 }
671}
672
673mod private {
674 use crate::{Dyncast, DyncastEq};
675
676 pub trait Upcast<T: ?Sized> {
677 // Based on this technique for object-safe `Self: Sized` defaults by @nbdd0121:
678 // <https://rust-lang.zulipchat.com/#narrow/stream/144729-wg-traits/topic/Dyn.20upcasting.20vs.20deref.20coercion/near/254603160>
679
680 fn upcast(&self) -> &T;
681 fn upcast_mut(&mut self) -> &mut T;
682 }
683 impl<'a, T> Upcast<dyn 'a + Dyncast> for T
684 where
685 T: 'a + Dyncast,
686 {
687 fn upcast(&self) -> &(dyn 'a + Dyncast) {
688 self
689 }
690
691 fn upcast_mut(&mut self) -> &mut (dyn 'a + Dyncast) {
692 self
693 }
694 }
695 impl<'a, T> Upcast<dyn 'a + DyncastEq> for T
696 where
697 T: 'a + DyncastEq,
698 {
699 fn upcast(&self) -> &(dyn 'a + DyncastEq) {
700 self
701 }
702
703 fn upcast_mut(&mut self) -> &mut (dyn 'a + DyncastEq) {
704 self
705 }
706 }
707}
708use private::Upcast;
709
710/// `not that useful yet` [`Dyncast`] and *dynamically* [`Eq`]
711//TODO: Other traits
712pub trait DyncastEq: Dyncast + for<'a> Upcast<dyn 'a + Dyncast> {
713 /// Upcasts this instance to [`Dyncast`].
714 fn as_dyncast<'a>(&self) -> &(dyn 'a + Dyncast)
715 where
716 Self: 'a,
717 {
718 self.upcast()
719 }
720
721 /// Mutably upcasts this instance to [`Dyncast`].
722 fn as_dyncast_mut<'a>(&mut self) -> &mut (dyn 'a + Dyncast)
723 where
724 Self: 'a,
725 {
726 self.upcast_mut()
727 }
728}
729impl<'a> Deref for dyn 'a + DyncastEq {
730 type Target = dyn 'a + Dyncast;
731
732 fn deref(&self) -> &Self::Target {
733 self.as_dyncast()
734 }
735}
736impl<'a> DerefMut for dyn 'a + DyncastEq {
737 fn deref_mut(&mut self) -> &mut Self::Target {
738 self.as_dyncast_mut()
739 }
740}
741impl<'a> Debug for dyn 'a + DyncastEq {
742 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
743 Debug::fmt(self.as_dyncast(), f)
744 }
745}
746impl<'a> Display for dyn 'a + DyncastEq {
747 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
748 Display::fmt(self.as_dyncast(), f)
749 }
750}
751impl<'a> PartialEq for dyn 'a + DyncastEq {
752 fn eq(&self, other: &Self) -> bool {
753 unsafe{self.dyncast_::<dyn 'a + PartialEq<dyn 'a + Dyncast>, dyn PartialEq<dyn Dyncast>>()}
754 .expect("Expected `Self` to be *dynamically* `dyn PartialEq<dyn Dyncast>` via `dyn DyncastOrd: PartialOrd`")
755 .eq(other.as_dyncast())
756 }
757}
758impl<'a> Eq for dyn 'a + DyncastEq {}
759impl<'a> Hash for dyn 'a + DyncastEq {
760 fn hash<H: Hasher>(&self, state: &mut H) {
761 self.as_dyncast().hash(state)
762 }
763}
764
765/// `not that useful yet` [`DyncastEq`] and *dynamically* [`Ord`]
766//TODO: Other traits
767pub trait DyncastOrd: DyncastEq + for<'a> Upcast<dyn 'a + DyncastEq> {
768 /// Upcasts this instance to [`DyncastEq`].
769 fn as_dyncast_eq<'a>(&self) -> &(dyn 'a + DyncastEq)
770 where
771 Self: 'a,
772 {
773 self.upcast()
774 }
775
776 /// Mutably upcasts this instance to [`DyncastEq`].
777 fn as_dyncast_eq_mut<'a>(&mut self) -> &mut (dyn 'a + DyncastEq)
778 where
779 Self: 'a,
780 {
781 self.upcast_mut()
782 }
783}
784impl<'a> Deref for dyn 'a + DyncastOrd {
785 type Target = dyn 'a + DyncastEq;
786
787 fn deref(&self) -> &Self::Target {
788 self.as_dyncast_eq()
789 }
790}
791impl<'a> DerefMut for dyn 'a + DyncastOrd {
792 fn deref_mut(&mut self) -> &mut Self::Target {
793 self.as_dyncast_eq_mut()
794 }
795}
796impl<'a> Debug for dyn 'a + DyncastOrd {
797 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
798 Debug::fmt(self.as_dyncast(), f)
799 }
800}
801impl<'a> Display for dyn 'a + DyncastOrd {
802 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
803 Display::fmt(self.as_dyncast(), f)
804 }
805}
806impl<'a> PartialEq for dyn 'a + DyncastOrd {
807 fn eq(&self, other: &Self) -> bool {
808 self.as_dyncast_eq().eq(other.as_dyncast_eq())
809 }
810}
811impl<'a> Eq for dyn 'a + DyncastOrd {}
812impl<'a> PartialOrd for dyn 'a + DyncastOrd {
813 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
814 unsafe{self.dyncast_::<dyn 'a+PartialOrd<dyn 'a+Dyncast>,dyn PartialOrd<dyn Dyncast>>()}
815 .expect("Expected `Self` to be *dynamically* `dyn PartialOrd<dyn Dyncast>` via `dyn DyncastOrd: PartialOrd`")
816 .partial_cmp(other.as_dyncast())
817 }
818}
819impl<'a> Ord for dyn 'a + DyncastOrd {
820 fn cmp(&self, other: &Self) -> Ordering {
821 unsafe{self.dyncast_::<dyn 'a+DynOrd, dyn DynOrd>()}
822 .expect("Expected `Self` to be *dynamically* `dyn PartialOrd<dyn DynOrd>` via `dyn DyncastOrd: Ord`")
823 .dyn_cmp(
824 unsafe{other
825 .dyncast_::<dyn 'a+DynOrd,dyn DynOrd>()}
826 .expect("Expected `other` to be *dynamically* `dyn PartialOrd<dyn DynOrd>` via `dyn DyncastOrd: Ord`")
827 )
828 }
829}
830impl<'a> Hash for dyn 'a + DyncastOrd {
831 fn hash<H: Hasher>(&self, state: &mut H) {
832 self.as_dyncast().hash(state)
833 }
834}
835
836#[doc(hidden)]
837pub mod __ {
838 #[cfg(feature = "macros")]
839 pub use static_assertions::const_assert;
840}