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 7 use std::{ 8 any::{Any, TypeId}, 9 collections::HashMap, 10 sync::Arc, 11 }; 12 13 use once_cell::sync::Lazy; 14 15 use 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)] 31 pub 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. 50 struct 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 61 impl 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. 180 pub(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] 192 macro_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)] 222 mod 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 }