object_safe/
lib.rs

1//! Do you need trait objects, in the form `dyn MyTrait`, that implement traits
2//! which are not object-safe?
3//!
4//! This crate solves the problem by providing object-safe traits which are
5//! analogous to some commonly used traits that are not object-safe, and
6//! auto-implementing both for a wide range of types. Currently, the following
7//! traits are supported:
8//! - Hash
9//! - PartialEq
10//! - Eq
11//!
12//! I plan to extend this support to other traits, and offer macros to simplify
13//! the process for custom traits.
14//!
15//! Learn about object safety here:
16//! https://doc.rust-lang.org/reference/items/traits.html#object-safety
17//!
18//! ## Example
19//!
20//! Let's take the `Hash` trait as an example. `Hash` is not object-safe. This
21//! means that `dyn Hash` is not a valid type in rust. Now, imagine you define
22//! this custom trait:
23//! ```rust ignore
24//! pub trait MyTrait: Hash {}
25//! ```
26//! Since `MyTrait` extends `Hash`, it is not object safe either, and `dyn
27//! MyTrait` is not a valid type. This crate offers a way to work around this
28//! limitation, so you can have object-safe traits whose objects implement
29//! object-unsafe traits such as `Hash`.
30//!
31//! Instead of expressing `Hash` as the trait bound, express `HashObj` as the
32//! trait bound.
33//! ```rust ignore
34//! pub trait MyTrait: HashObj {}
35//! ```
36//!
37//! You do not need to implement `HashObj`. It is automatically implemented for
38//! any type that implements `Hash`. Now, `dyn MyTrait` is object-safe. Add one
39//! line of code if you want `dyn MyTrait` to implement `Hash`:
40//!
41//! ```rust ignore
42//! impl_hash(dyn MyTrait);
43//! ```
44//!
45//! Here are all the characteristics that come with HashObj:
46//! - anything implementing `Hash` automatically implements `HashObj`
47//! - `dyn HashObj` implements `Hash`.
48//! - `Obj<T>` implements `Hash` for any `T` that derefs to something
49//!   implementing `HashObj`.
50//! - `impl_hash` can implement `Hash` for any type that implements `HashObj`,
51//!   for example a trait object `dyn MyTrait` where `MyTrait` is a trait
52//!   extending `HashObj`.
53//!
54//! ```rust ignore
55//! impl_hash! {
56//!     // typical use, where MyTrait: HashObj
57//!     dyn MyTrait,
58//!     dyn AnotherTrait,
59//!
60//!     // structs and enums are supported if they deref to
61//!     // a target that implements HashObj or Hash.
62//!     MyStruct,
63//!
64//!     // special syntax for generics.
65//!     MySimpleGeneric<T> where <T>,
66//!     MyGenericType<T, F> where <T, F: HashObj>,
67//!     dyn MyGenericTrait<T> where <T: SomeTraitBound>,
68//!
69//!     // the actual impl for Obj
70//!     Obj<T> where <T: Deref<Target=X>, X: HashObj + ?Sized>,
71//! }
72//! ```
73
74use core::{
75    any::Any,
76    hash::{Hash, Hasher},
77    ops::Deref,
78};
79
80mod obj;
81
82pub use obj::Obj;
83
84/// Helper trait to enable trait upcasting, since upcasting is not stable.
85pub trait AsAny: Any {
86    fn as_any(&self) -> &dyn Any;
87}
88
89impl<T: Any> AsAny for T {
90    fn as_any(&self) -> &(dyn Any) {
91        self as &dyn Any
92    }
93}
94
95/// Object-safe version of Eq
96pub trait EqObj: PartialEqObj {
97    fn as_eq_object(&self) -> &dyn EqObj;
98    fn to_eq_object(self) -> Box<dyn EqObj>;
99}
100
101impl<T> EqObj for T
102where
103    T: Eq + PartialEqObj,
104{
105    fn as_eq_object(&self) -> &dyn EqObj {
106        self
107    }
108
109    fn to_eq_object(self) -> Box<dyn EqObj> {
110        Box::new(self)
111    }
112}
113
114impl_eq! {
115    Obj<T> where <T: Deref<Target=X>, X: EqObj + ?Sized>,
116    dyn EqObj,
117}
118
119#[macro_export]
120macro_rules! impl_eq {
121    ($(
122        $Type:ty $(where <$(
123            $G:ident$(:
124                $($Gb:ident $(<$($GbIn:ident$(=$GbInEq:ty)?)+>)?)?
125                $(?$Gbq:ident)?
126                $(
127                    +
128                    $($Gb2:ident $(<$($GbIn2:ident$(=$GbInEq2:ty)?)+>)?)?
129                    $(?$Gbq2:ident)?
130                )*
131            )?
132        ),+>)?
133    ),*$(,)?) => {$(
134        impl$(<$(
135            $G$(:
136                $($Gb $(<$($GbIn$(=$GbInEq)?)+>)?)?
137                $(?$Gbq)?
138                $(
139                    +
140                    $($Gb2 $({$($GbIn2$(=$GbInEq2:ty)?)+})?)?
141                    $(?$Gbq2)?
142                )*
143            )?
144        ),+>)?
145        Eq for $Type where $Type: 'static {})*
146    };
147}
148
149/// Object-safe version of PartialEq
150pub trait PartialEqObj: AsAny {
151    fn eq_object(&self, other: &dyn PartialEqObj) -> bool;
152    fn as_partial_eq_object(&self) -> &dyn PartialEqObj;
153    fn to_partial_eq_object(self) -> Box<dyn PartialEqObj>;
154}
155
156impl<T> PartialEqObj for T
157where
158    T: PartialEq + AsAny,
159{
160    fn eq_object(&self, other: &dyn PartialEqObj) -> bool {
161        match other.as_any().downcast_ref::<Self>() {
162            Some(other) => self == other,
163            None => false,
164        }
165    }
166
167    fn as_partial_eq_object(&self) -> &dyn PartialEqObj {
168        self
169    }
170
171    fn to_partial_eq_object(self) -> Box<dyn PartialEqObj> {
172        Box::new(self)
173    }
174}
175
176impl_partial_eq! {
177    Obj<T> where <T: Deref<Target=X>, X: PartialEqObj + ?Sized>,
178    dyn PartialEqObj,
179    dyn EqObj,
180}
181
182#[macro_export]
183macro_rules! impl_partial_eq {
184    ($(
185        $Type:ty $(where <$(
186            $G:ident$(:
187                $($Gb:ident $(<$($GbIn:ident$(=$GbInEq:ty)?)+>)?)?
188                $(?$Gbq:ident)?
189                $(
190                    +
191                    $($Gb2:ident $(<$($GbIn2:ident$(=$GbInEq2:ty)?)+>)?)?
192                    $(?$Gbq2:ident)?
193                )*
194            )?
195        ),+>)?
196    ),*$(,)?) => {$(
197        impl$(<$(
198            $G$(:
199                $($Gb $(<$($GbIn$(=$GbInEq)?)+>)?)?
200                $(?$Gbq)?
201                $(
202                    +
203                    $($Gb2 $({$($GbIn2$(=$GbInEq2:ty)?)+})?)?
204                    $(?$Gbq2)?
205                )*
206            )?
207        ),+>)?
208        PartialEq for $Type where $Type: 'static {
209            fn eq(&self, other: &Self) -> bool {
210                self.deref().eq_object(other.deref().as_partial_eq_object())
211            }
212        })*
213    };
214}
215
216/// Object-safe version of `std::hash::Hash`
217pub trait HashObj {
218    fn hash_object(&self, state: &mut dyn Hasher);
219    fn as_hash_object(&self) -> &dyn HashObj;
220    fn to_hash_object(self) -> Box<dyn HashObj>
221    where
222        Self: 'static;
223}
224
225impl<T: Hash> HashObj for T {
226    fn hash_object(&self, mut state: &mut dyn Hasher) {
227        self.hash(&mut state);
228    }
229
230    fn as_hash_object(&self) -> &dyn HashObj {
231        self
232    }
233
234    fn to_hash_object(self) -> Box<dyn HashObj>
235    where
236        Self: 'static,
237    {
238        Box::new(self)
239    }
240}
241
242impl_hash! {
243    Obj<T> where <T: Deref<Target=X>, X: HashObj + ?Sized>,
244    dyn HashObj,
245}
246
247#[macro_export]
248macro_rules! impl_hash {
249    ($(
250        $Type:ty $(where <$(
251            $G:ident$(:
252                $($Gb:ident $(<$($GbIn:ident$(=$GbInEq:ty)?)+>)?)?
253                $(?$Gbq:ident)?
254                $(
255                    +
256                    $($Gb2:ident $(<$($GbIn2:ident$(=$GbInEq2:ty)?)+>)?)?
257                    $(?$Gbq2:ident)?
258                )*
259            )?
260        ),+>)?
261    ),*$(,)?) => {$(
262        impl$(<$(
263            $G$(:
264                $($Gb $(<$($GbIn$(=$GbInEq)?)+>)?)?
265                $(?$Gbq)?
266                $(
267                    +
268                    $($Gb2 $({$($GbIn2$(=$GbInEq2:ty)?)+})?)?
269                    $(?$Gbq2)?
270                )*
271            )?
272        ),+>)?
273        std::hash::Hash for $Type {
274            fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
275                self.deref().hash_object(state);
276            }
277        }
278    )*};
279}
280
281#[cfg(test)]
282mod test {
283    use std::collections::hash_map::DefaultHasher;
284
285    use super::*;
286
287    #[test]
288    fn eq() {
289        let x: Box<dyn EqObj> = Box::new(10);
290        let y: Box<dyn EqObj> = Box::new(10);
291        let z: Box<dyn EqObj> = Box::new(11);
292        if x != y {
293            panic!("should be equal")
294        }
295        if x == z {
296            panic!("should not be equal")
297        }
298    }
299
300    #[test]
301    fn hash_works() {
302        let x: &str = "Hello, World!";
303        let y: &dyn HashObj = "Hello, World!".as_hash_object();
304        let z: &dyn HashObj = "banana".as_hash_object();
305        assert_eq!(hash(x), hash(y));
306        assert_ne!(hash(y), hash(z));
307    }
308
309    fn hash<T: Hash>(t: T) -> u64 {
310        let mut hasher = DefaultHasher::new();
311        t.hash(&mut hasher);
312        hasher.finish()
313    }
314
315    mod obj_tests {
316        use crate::*;
317        /// compiler test: hash
318        trait MyHash: HashObj {}
319        #[derive(Hash)]
320        struct MyHashWrapper(Obj<Box<dyn MyHash>>);
321        impl<T> MyHash for T where T: Hash {}
322
323        /// compiler test: partial eq
324        trait MyPartialEq: PartialEqObj {}
325        #[derive(PartialEq)]
326        struct MyPartialEqWrapper(Obj<Box<dyn MyPartialEq>>);
327        impl<T> MyPartialEq for T where T: PartialEq + 'static {}
328
329        /// compiler test: eq
330        trait MyEq: EqObj + PartialEqObj + std::fmt::Debug {}
331        #[derive(PartialEq, Eq, Debug)]
332        struct MyEqWrapper(Obj<Box<dyn MyEq>>);
333        impl<T> MyEq for T where T: Eq + 'static + std::fmt::Debug {}
334
335        #[test]
336        fn hash_obj_works() {
337            let a = super::hash(0);
338            let b = super::hash(Box::new(0));
339            let c = super::hash(Obj(Box::new(0)));
340            let d = super::hash(MyHashWrapper(Obj(Box::new(0))));
341            assert_eq!(a, b);
342            assert_eq!(a, c);
343            assert_eq!(a, d);
344        }
345
346        #[test]
347        fn obj_box_dyn_custom_eq() {
348            assert_eq!(
349                Obj(Box::new(0) as Box<dyn MyEq>),
350                Obj(Box::new(0) as Box<dyn MyEq>)
351            );
352            assert_ne!(
353                Obj(Box::new(0) as Box<dyn MyEq>),
354                Obj(Box::new(1) as Box<dyn MyEq>)
355            );
356        }
357
358        #[test]
359        fn wrapped_obj_box_dyn_custom_eq() {
360            assert_eq!(
361                MyEqWrapper(Obj(Box::new(0) as Box<dyn MyEq>)),
362                MyEqWrapper(Obj(Box::new(0) as Box<dyn MyEq>))
363            );
364            assert_ne!(
365                MyEqWrapper(Obj(Box::new(0) as Box<dyn MyEq>)),
366                MyEqWrapper(Obj(Box::new(1) as Box<dyn MyEq>))
367            );
368        }
369
370        #[test]
371        fn obj_box_eq() {
372            assert_eq!(Obj(Box::new(0)), Obj(Box::new(0)));
373            assert_ne!(Obj(Box::new(0)), Obj(Box::new(1)));
374        }
375
376        #[test]
377        fn wrapped_ob_box_eq() {
378            assert_eq!(MyEqWrapper(Obj(Box::new(0))), MyEqWrapper(Obj(Box::new(0))));
379            assert_ne!(MyEqWrapper(Obj(Box::new(0))), MyEqWrapper(Obj(Box::new(1))));
380        }
381    }
382
383    mod impl_tests {
384        use crate::*;
385        trait MyTrait: HashObj + EqObj + PartialEqObj {}
386        impl<T> MyTrait for T where T: Hash + Eq + PartialEq + 'static {}
387
388        impl_hash!(dyn MyTrait);
389        impl_eq!(dyn MyTrait);
390        impl_partial_eq!(dyn MyTrait);
391
392        #[test]
393        fn box_dyn_custom_eq() {
394            if Box::new(0) as Box<dyn MyTrait> != Box::new(0) as Box<dyn MyTrait> {
395                panic!("should be equal");
396            }
397            if Box::new(0) as Box<dyn MyTrait> == Box::new(1) as Box<dyn MyTrait> {
398                panic!("should not be equal");
399            }
400        }
401    }
402}
403
404// /// TODO:
405// /// - handle different method signature between declaration and definition
406// /// - create impl_* macro
407// /// - better syntax, find a way around square brackets
408// /// - converting this to a proc macro is probably best
409// ///
410// /// wip! {
411// ///     PartialEq: AsAny {
412// ///         [fn eq_object(&self, other: &dyn PartialEqObject) -> bool] {
413// ///             match other.as_any().downcast_ref::<Self>() {
414// ///                 Some(other) => self == other,
415// ///                 None => false,
416// ///             }
417// ///         }
418// ///     }
419// ///
420// ///     Eq: PartialEqObject {}
421// /// }
422// #[allow(unused)]
423// macro_rules! wip {
424//     (
425//         $(
426//             $Trait:ty $(: $($TraitBound:ty)+)? $(where T: $($ImplBound:ty)+)?
427//             {$(
428//                 [$($fn_sig:tt)*]
429//                 $fn_impl:block
430//             )*}
431//         )*
432//     ) => {$(paste::paste!{
433//         pub trait [<$Trait Object>] $(: $($TraitBound)++)? {
434//             fn [<as_ $Trait:snake _object>](&self) -> &dyn [<$Trait Object>];
435
436//             $($($fn_sig)*;)*
437//         }
438
439//         impl<T> [<$Trait Object>] for T
440//         where
441//             T: $Trait $($(+ $TraitBound)+)?,
442//         {
443//             fn [<as_ $Trait:snake _object>](&self) -> &dyn [<$Trait Object>] {
444//                 self
445//             }
446
447//             $($($fn_sig)* {$fn_impl})*
448//         }
449//     })*};
450// }