Опытные Rust-программисты знают, что в Rust можно сделать мапу, которая индексируется не рантайм-ключами, а типами. Сделать это довольно просто поверх обычной мапы: достаточно хранить значения в виде
Box<dyn Any>, а в качестве ключей использовать TypeId (хотя и есть немного неочевидный нюанс). В геттере мы конструируем TypeId от переданного типа, индексируем им мапу, и если значение есть, то приводим к нужному типа через downcast_ref/downcast_mut.#[derive(Default)]
struct TypeMap(HashMap<TypeId, Box<dyn Any>>);
impl TypeMap {
fn insert<T: 'static>(&mut self, val: T) {
self.0.insert(TypeId::of::<T>(), Box::new(val));
}
fn get<T: 'static>(&self) -> Option<&T> {
self.0.get(&TypeId::of::<T>())?.downcast_ref::<T>()
}
}
fn main() {
let mut map = TypeMap::default();
map.insert(42_i32);
assert_eq!(map.get::<i32>(), Some(&42));
}
С дополнительными обёртками можно делать мапы с отображениями из
T в тип, параметризованный T:T -> Option<T>
T -> [T; 2]
T -> Vec<T>
В общем, принцип понятен. Но что делать, если мы хотим хранить в параметризованной типами мапе коллбеки, связанные с типом?Пусть для определённости мы хотим хранить коллбеки вида
FnMut(&mut T). Как я уже говорил, в Rust нет общего типа для замыканий одной сигнатуры — есть лишь общий трейт. Для того, чтобы сохранить замыкание в TypeMap, нам потребуется каким-то образом стереть тип дважды: сначала от конкретного типа замыкания до dyn FnMut(&mut T), а потом от него до dyn Any.Наиболее простой способ сделать это — забоксить замыкание дважды:
// определение `TypeMap` выше
#[derive(Default)]
struct CallbackMap(TypeMap);
impl CallbackMap {
fn insert<T: 'static>(&mut self, f: impl FnMut(&mut T) + 'static) {
let boxed = Box::new(f) as Box<dyn FnMut(&mut T) + 'static>;
self.0.insert(boxed);
}
fn get_by_arg<T: 'static>(&mut self) -> Option<&mut (dyn FnMut(&mut T) + 'static)> {
self.0.get_mut::<Box<dyn FnMut(&mut T) + 'static>>().map(|f| &mut **f)
}
}
fn main() {
let mut map = CallbackMap::default();
let multiplier = 7;
let f = move |x: &mut i32| *x *= multiplier;
map.insert(f);
let mut x = 6;
map.get_by_arg().unwrap()(&mut x);
assert_eq!(x, 42);
}