tor_rpcbase/obj/
cast.rs

1//! Casting objects to trait pointers.
2//!
3//! Rust supports Any-to-Concrete downcasting via Any;
4//! and the `downcast_rs` crate supports Trait-to-Concrete downcasting.
5//! This module adds `Trait-to-Trait` downcasting for the Object trait.
6
7use std::{
8    any::{Any, TypeId},
9    collections::HashMap,
10    sync::Arc,
11};
12
13use once_cell::sync::Lazy;
14
15use crate::Object;
16
17/// A collection of functions to downcast `&dyn Object` references for some
18/// particular concrete object type `O` into various `&dyn Trait` references.
19///
20/// You shouldn't construct this on your own: instead use
21/// `derive_deftly(Object)`.
22///
23/// You shouldn't use this directly; instead use
24/// [`ObjectArcExt`](super::ObjectArcExt).
25///
26/// Note that the concrete object type `O`
27/// is *not* represented in the type of `CastTable`;
28/// `CastTable`s are obtained and used at runtime, as part of dynamic dispatch,
29/// so the type `O` is erased.  We work with `TypeId`s and various `&dyn ...`.
30#[derive(Default)]
31pub struct CastTable {
32    /// A mapping from target TypeId for some trait to a function that can
33    /// convert this table's type into a trait pointer to that trait.
34    ///
35    /// Every entry in this table must contain:
36    ///
37    ///   * A key that is `typeid::of::<&'static dyn Tr>()` for some trait `Tr`.
38    ///   * A [`Caster`] whose functions are suitable for casting objects from this table's
39    ///     type to `dyn Tr`.
40    table: HashMap<TypeId, Caster>,
41}
42
43/// A single entry in a `CastTable`.
44///
45/// Each `Caster` exists for one concrete object type "`O`", and one trait type "`Tr`".
46///
47/// Note that we use `Box` here in order to support generic types: you can't
48/// get a `&'static` reference to a function that takes a generic type in
49/// current rust.
50struct Caster {
51    /// Actual type: `fn(Arc<dyn Object>) -> Arc<dyn Tr>`
52    ///
53    /// Panics if Object does not have the expected type (`O`).
54    cast_to_ref: Box<dyn Any + Send + Sync>,
55    /// Actual type: `fn(Arc<dyn Object>) -> Arc<dyn Tr>`
56    ///
57    /// Panics if Object does not have the expected type (`O`).
58    cast_to_arc: Box<dyn Any + Send + Sync>,
59}
60
61impl CastTable {
62    /// Add a new entry to this `CastTable` for downcasting to TypeId.
63    ///
64    /// You should not call this yourself; instead use
65    /// [`derive_deftly(Object)`](crate::templates::derive_deftly_template_Object)
66    ///
67    /// # Requirements
68    ///
69    /// `T` must be `dyn Tr` for some trait `Tr`.
70    /// (Not checked by the compiler.)
71    ///
72    /// `cast_to_ref` is a downcaster from `&dyn Object` to `&dyn Tr`.
73    ///
74    /// `cast_to_arc` is a downcaster from `Arc<dyn Object>` to `Arc<dyn Tr>`.
75    ///
76    /// These functions SHOULD
77    /// panic if the concrete type of its argument is not the concrete type `O`
78    /// associated with this `CastTable`.
79    ///
80    /// `O` must be `'static`.
81    /// (Checked by the compiler.)
82    ///
83    /// # Panics
84    ///
85    /// Panics if called twice on the same `CastTable` with the same `Tr`.
86    //
87    // `TypeId::of::<dyn SomeTrait + '_>` exists, but is not the same as
88    // `TypeId::of::<dyn SomeTrait + 'static>` (unless `SomeTrait: 'static`).
89    //
90    // We avoid a consequent bug with non-'static traits as follows:
91    // We insert and look up by `TypeId::of::<&'static dyn SomeTrait>`,
92    // which must mean `&'static (dyn SomeTrait + 'static)`
93    // since a 'static reference to anything non-'static is an ill-formed type.
94    pub fn insert<T: 'static + ?Sized>(
95        &mut self,
96        cast_to_ref: fn(&dyn Object) -> &T,
97        cast_to_arc: fn(Arc<dyn Object>) -> Arc<T>,
98    ) {
99        let type_id = TypeId::of::<&'static T>();
100        let caster = Caster {
101            cast_to_ref: Box::new(cast_to_ref),
102            cast_to_arc: Box::new(cast_to_arc),
103        };
104        self.insert_erased(type_id, caster);
105    }
106
107    /// Implementation for adding an entry to the `CastTable`
108    ///
109    /// Broken out for clarity and to reduce monomorphisation.
110    ///
111    /// ### Requirements
112    ///
113    /// Like `insert`, but less compile-time checking.
114    /// `type_id` is the identity of `&'static dyn Tr`,
115    /// and `func` has been boxed and type-erased.
116    fn insert_erased(&mut self, type_id: TypeId, caster: Caster) {
117        let old_val = self.table.insert(type_id, caster);
118        assert!(
119            old_val.is_none(),
120            "Tried to insert a duplicate entry in a cast table.",
121        );
122    }
123
124    /// Try to downcast a reference to an object whose concrete type is
125    /// `O` (the type associated with this `CastTable`)
126    /// to some target type `T`.
127    ///
128    /// `T` should be `dyn Tr`.
129    /// If `T` is not one of the `dyn Tr` for which `insert` was called,
130    /// returns `None`.
131    ///
132    /// # Panics
133    ///
134    /// Panics if the concrete type of `obj` does not match `O`.
135    ///
136    /// May panic if any of the Requirements for [`CastTable::insert`] were
137    /// violated.
138    pub fn cast_object_to<'a, T: 'static + ?Sized>(&self, obj: &'a dyn Object) -> Option<&'a T> {
139        let target_type = TypeId::of::<&'static T>();
140        let caster = self.table.get(&target_type)?;
141        let caster: &fn(&dyn Object) -> &T = caster
142            .cast_to_ref
143            .downcast_ref()
144            .expect("Incorrect cast-function type found in cast table!");
145        Some(caster(obj))
146    }
147
148    /// As [`cast_object_to`](CastTable::cast_object_to), but returns an `Arc<dyn Tr>`.
149    ///
150    /// If `T` is not one of the `dyn Tr` types for which `insert_arc` was called,
151    /// return `Err(obj)`.
152    ///
153    /// # Panics
154    ///
155    /// Panics if the concrete type of `obj` does not match `O`.
156    ///
157    /// May panic if any of the Requirements for [`CastTable::insert`] were
158    /// violated.
159    pub fn cast_object_to_arc<T: 'static + ?Sized>(
160        &self,
161        obj: Arc<dyn Object>,
162    ) -> Result<Arc<T>, Arc<dyn Object>> {
163        let target_type = TypeId::of::<&'static T>();
164        let caster = match self.table.get(&target_type) {
165            Some(c) => c,
166            None => return Err(obj),
167        };
168        let caster: &fn(Arc<dyn Object>) -> Arc<T> = caster
169            .cast_to_arc
170            .downcast_ref()
171            .expect("Incorrect cast-function type found in cast table!");
172        Ok(caster(obj))
173    }
174}
175
176/// Static cast table that doesn't support casting anything to anything.
177///
178/// Because this table doesn't support any casting, it is okay to use it with
179/// any concrete type.
180pub(super) static EMPTY_CAST_TABLE: Lazy<CastTable> = Lazy::new(|| CastTable {
181    table: HashMap::new(),
182});
183
184/// Helper for HasCastTable to work around derive-deftly#36.
185///
186/// Defines the body for a private make_cast_table() method.
187///
188/// This macro is not part of `tor-rpcbase`'s public API, and is not covered
189/// by semver guarantees.
190#[doc(hidden)]
191#[macro_export]
192macro_rules! cast_table_deftness_helper{
193    // Note: We have to use tt here, since $ty can't be used in $(dyn .)
194    { $( $traitname:tt ),* } => {
195                #[allow(unused_mut)]
196                let mut table = $crate::CastTable::default();
197                $({
198                    use std::sync::Arc;
199                    // These are the actual functions that does the downcasting.
200                    // It works by downcasting with Any to the concrete type, and then
201                    // upcasting from the concrete type to &dyn Trait.
202                    let cast_to_ref: fn(&dyn $crate::Object) -> &(dyn $traitname + 'static) = |self_| {
203                        let self_: &Self = self_.downcast_ref().unwrap();
204                        let self_: &dyn $traitname = self_ as _;
205                        self_
206                    };
207                    let cast_to_arc: fn(Arc<dyn $crate::Object>) -> Arc<dyn $traitname> = |self_| {
208                        let self_: Arc<Self> = self_
209                            .downcast_arc()
210                            .ok()
211                            .expect("used with incorrect type");
212                        let self_: Arc<dyn $traitname> = self_ as _;
213                        self_
214                    };
215                    table.insert::<dyn $traitname>(cast_to_ref, cast_to_arc);
216                })*
217                table
218    }
219}
220
221#[cfg(test)]
222mod test {
223    // @@ begin test lint list maintained by maint/add_warning @@
224    #![allow(clippy::bool_assert_comparison)]
225    #![allow(clippy::clone_on_copy)]
226    #![allow(clippy::dbg_macro)]
227    #![allow(clippy::mixed_attributes_style)]
228    #![allow(clippy::print_stderr)]
229    #![allow(clippy::print_stdout)]
230    #![allow(clippy::single_char_pattern)]
231    #![allow(clippy::unwrap_used)]
232    #![allow(clippy::unchecked_duration_subtraction)]
233    #![allow(clippy::useless_vec)]
234    #![allow(clippy::needless_pass_by_value)]
235    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
236
237    use super::*;
238    use crate::templates::*;
239    use derive_deftly::Deftly;
240
241    trait Tr1 {}
242    trait Tr2: 'static {}
243
244    #[derive(Deftly)]
245    #[derive_deftly(Object)]
246    #[deftly(rpc(downcastable_to = "Tr1"))]
247    struct Simple;
248    impl Tr1 for Simple {}
249
250    #[test]
251    fn check_simple() {
252        let concrete = Simple;
253        let tab = Simple::make_cast_table();
254        let obj: &dyn Object = &concrete;
255        let _cast: &(dyn Tr1 + '_) = tab.cast_object_to(obj).expect("cast failed");
256
257        let arc = Arc::new(Simple);
258        let arc_obj: Arc<dyn Object> = arc.clone();
259        let _cast: Arc<dyn Tr1> = tab.cast_object_to_arc(arc_obj).ok().expect("cast failed");
260    }
261
262    #[derive(Deftly)]
263    #[derive_deftly(Object)]
264    #[deftly(rpc(downcastable_to = "Tr1, Tr2"))]
265    struct Generic<T: Send + Sync + 'static>(T);
266
267    impl<T: Send + Sync + 'static> Tr1 for Generic<T> {}
268    impl<T: Send + Sync + 'static> Tr2 for Generic<T> {}
269
270    #[test]
271    fn check_generic() {
272        let gen: Generic<&'static str> = Generic("foo");
273        let tab = Generic::<&'static str>::make_cast_table();
274        let obj: &dyn Object = &gen;
275        let _cast: &(dyn Tr1 + '_) = tab.cast_object_to(obj).expect("cast failed");
276
277        let arc = Arc::new(Generic("bar"));
278        let arc_obj: Arc<dyn Object> = arc.clone();
279        let _cast: Arc<dyn Tr2> = tab.cast_object_to_arc(arc_obj).ok().expect("cast failed");
280    }
281}