For context: I’m implementing an Entity, basically a thin wrapper around a heterogenous map, and trying to implement an update method:
struct Entity {
pub id: u64,
data: HashMap<TypeId, Box<dyn Any>>,
}
impl Entity {
pub fn new(id: u64) -> Self {
Entity { id, data: HashMap::new() }
}
pub fn get_atom<T: Any>(&self) -> Option<&T> {
self.data.get(&TypeId::of::<T>())?.downcast_ref()
}
pub fn set_atom<T: Any>(&mut self, value: T) {
self.data.insert(TypeId::of::<T>(), Box::new(value));
}
pub fn update<T: Any, R: Any>(&mut self, f: impl Fn(&T) -> R)
{
if let Some(val) = self.get_atom() {
self.set_atom(f(val));
}
}
}
This works, but gives a warning:
warning: cannot borrow `*self` as mutable because it is also borrowed as immutable
--> src/entity.rs:71:13
|
70 | if let Some(val) = self.get_atom() {
| --------------- immutable borrow occurs here
71 | self.set_atom(f(val));
| ^^^^^^^^^^^^^^^^---^^
| | |
| | immutable borrow later used here
| mutable borrow occurs here
OK, that’s fixable like this, which removes the warning.
pub fn update<T: Any, R: Any>(&mut self, f: impl Fn(&T) -> R)
{
let maybe : Option<R> = self.get_atom().map(f);
if let Some(val) = maybe {
self.set_atom(val);
}
}
The problem I have is when I introduce a trait for abstracting over getting stuff out of the entity (which permits me to abstract over arity: getting a tuple returns a value if each of the components is in the entity):
trait GetComponent<'a, T> {
fn get_component(entity: &'a Entity) -> Option<T>;
}
// sample atom impl
impl <'a> GetComponent<'a, &'a u32> for &'a u32 {
fn get_component(entity: &'a Entity) -> Option<&'a u32> {
entity.get_atom()
}
}
// sample composite impl
impl <'a, A, B> GetComponent<'a, (A, B)> for (A, B) where
A: GetComponent<'a, A>,
B: GetComponent<'a, B>,
{
fn get_component(entity: &'a Entity) -> Option<(A, B)> {
Some((A::get_component(entity)?, B::get_component(entity)?))
}
}
impl Entity {
<in addition to the above>
pub fn get<'a, T: GetComponent<'a, T>>(&'a self) -> Option<T> {
T::get_component(self)
}
pub fn update<'a, T: 'a, R: Any>(&'a mut self, f: impl Fn(&T) -> R)
where &'a T: GetComponent<'a, &'a T>
{
let maybe : Option<R> = self.get().map(f);
if let Some(val) = maybe {
self.set_atom(val);
}
}
}
The trait requires me to be explicit about lifetimes, and everything else I’ve tried to do so far with the entity seems to be just fine. But this update method gives this compile error (not a warning this time):
error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable
--> src/entity.rs:56:13
|
51 | pub fn update<'a, T: 'a, R: Any>(&'a mut self, f: impl Fn(&T) -> R)
| -- lifetime `'a` defined here
...
54 | let maybe : Option<R> = self.get().map(f);
| ----------
| |
| immutable borrow occurs here
| argument requires that `*self` is borrowed for `'a`
55 | if let Some(val) = maybe {
56 | self.set_atom(val);
| ^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
Now, as far as I can see, there’s no reason why I shouldn’t be able to immutably borrow for a shorter period, so the immutable borrow "expires" before I do the mutation – just like in the version without explicit lifetimes used prior to the trait.
But I can’t for the life of me work out how to make this work.
>Solution :
You can solve this with a higher-rank trait bound (HRTB):
pub fn update<T, R: Any>(&mut self, f: impl Fn(&T) -> R)
where for <'a> &'a T: GetComponent<'a, &'a T>
This says that for any possible lifetime 'a, T must satisfy the given bound. This allows you to specify how the various lifetimes on the type T relate to each other without binding them to a specific lifetime, such as that of &mut self.