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}