I'm trying to create a component system where any component can "publish" an instance of a type to components rendering under it, which is indexable by it's type. It resembles Flutter's BuilderContext.
I simplified the example to the following:
use std::any::{Any, TypeId};
use std::collections::HashMap;
// State
pub struct Ctx {
map: HashMap<TypeId, Box<dyn Any>>
}
impl Ctx {
pub fn new() -> Self {
Ctx {
map: HashMap::new()
}
}
pub fn inject<W, T: 'static, F: Fn(&mut Self) -> W>(&mut self, value: T, cb: F) -> W {
let maybe_old_val = self.map.insert(TypeId::of::<T>(), Box::new(value));
let ret_val = cb(self);
match maybe_old_val {
Some(old_val) => {
self.map.insert(TypeId::of::<T>(), old_val);
}
None => {
self.map.remove(&TypeId::of::<T>());
}
};
ret_val
}
pub fn get<T: 'static>(&self) -> Option<&T> {
match self.map.get(&TypeId::of::<T>()) {
Some(the_box) => {
the_box.downcast_ref().take()
}
None => None
}
}
pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
match self.map.get_mut(&TypeId::of::<T>()) {
Some(the_box) => {
the_box.downcast_mut().take()
}
None => None
}
}
}
fn main() {
let mut ctx = Ctx::new();
println!("outer f64: {:?}", ctx.get::<f64>());
println!("outer i32: {:?}", ctx.get::<i32>());
ctx.inject(123.0, |ctx| {
println!("inner f64: {:?}", ctx.get::<f64>());
println!("inner i32: {:?}", ctx.get::<i32>());
ctx.inject(321, |ctx| {
println!("inner-er f64: {:?}", ctx.get::<f64>());
println!("inner-er i32: {:?}", ctx.get::<i32>());
});
});
}
This gives:
outer f64: None
outer i32: None
inner f64: Some(123.0)
inner i32: None
inner-er f64: Some(123.0)
inner-er i32: Some(321)
As expected.
But if I try to pass a reference to something on the stack (In the real application I'm passing SDL2's context, it cannot be static), the compiler gives an error:
struct ABC(i32);
fn main() {
let mut ctx = Ctx::new();
let abc = ABC(123);
ctx.inject(&abc, |ctx| {
println!("inner abc: {:?}", ctx.get::<&ABC>().unwrap().0);
});
}
error[E0597]: `abc` does not live long enough
--> src/main.rs:56:16
|
56 | ctx.inject(&abc, |ctx| {
| - ^^^^ borrowed value does not live long enough
| _____|
| |
57 | | println!("inner abc: {:?}", ctx.get::<&ABC>().unwrap().0);
58 | | });
| |______- argument requires that `abc` is borrowed for `'static`
59 | }
| - `abc` dropped here while still borrowed
I noticed that encasing the struct in a Box
works, but I see no reason to allocate it on the stack (also I want to support passing references, I may not own everything, or it might be stored in a struct)
Cloning is also not a solution because they may not be clonable (like the SDL2 context) or be very big (image or video resources).
I have been trying to solve this for hours now, any ideas?