So, one problem here is that anything with mutable access to the HashMap
can just replace the whole HashMap
/ drop all the items in it, etc. This is why you can’t simply hold a reference “into” the map while offering some other place in the code mutable access.
Using Rust’s types for interior mutability (RefCell
) and shared ownership (Rc
) can make this work though. The Box<dyn Iface>
becomes an Rc<RefCell<dyn Iface>>
, this way a reference to an item in the map can be obtained by creating a new copy of the reference-counted pointer. Mutable access through RefCell
implements dynamic checks, in effect “locking” items while you’re mutating them in one place. (This means that due to the RefCell
, a call-back directly or indirectly calling itself will result in a panic; probably not a problem though, since in your original approach, the call-back didn’t exist in the map at all while it was running.)
I’m not sure about the performance implications when compared to the “removing and putting back in” approach, anyways, here’s an adaptation of your code example.
use std::{cell::RefCell, collections::HashMap, rc::Rc};
trait Iface {
fn fun(&mut self, c: &mut Container);
}
struct Container {
map: HashMap<i32, Rc<RefCell<dyn Iface>>>,
}
impl Container {
fn do_it(&mut self) {
if let Some(i) = self.map.get(&123) {
let i = Rc::clone(i);
i.borrow_mut().fun(self);
} else {
panic!("unexpected");
}
}
}
struct Thing {}
impl Iface for Thing {
fn fun(&mut self, c: &mut Container) {
c.map.insert(456, Rc::new(RefCell::new(Thing {})));
}
}
fn main() {
let mut c = Container {
map: HashMap::new(),
};
c.map.insert(123, Rc::new(RefCell::new(Thing {})));
c.do_it();
for key in c.map.keys() {
println!("{}", key);
}
}
(in the playground)
Note that I’m assuming here that the &mut self
argument in Iface
is necessary, even though your Thing
doesn’t actually use it. Without that, you could skip the RefCell
. (You could still add a RefCell
or Cell
or similar to fields of an Iface
implementor, in case you do need mutation; but it would be a more fine-grained approach.)
use std::{collections::HashMap, rc::Rc};
trait Iface {
fn fun(&self, c: &mut Container);
}
struct Container {
map: HashMap<i32, Rc<dyn Iface>>,
}
impl Container {
fn do_it(&mut self) {
if let Some(i) = self.map.get(&123) {
let i = Rc::clone(i);
i.fun(self);
} else {
panic!("unexpected");
}
}
}
struct Thing {}
impl Iface for Thing {
fn fun(&self, c: &mut Container) {
c.map.insert(456, Rc::new(Thing {}));
}
}
fn main() {
let mut c = Container {
map: HashMap::new(),
};
c.map.insert(123, Rc::new(Thing {}));
c.do_it();
for key in c.map.keys() {
println!("{}", key);
}
}
(in the playground)
The types presented in this answer aren’t thread safe (i.e. Rust won’t let you use them across multiple threads). If you need those capabilities, there are the types Arc
to replace Rc
, and typically Mutex
takes the role of RefCell
.