Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

How to allow immutable and mutable borrows to coexist in a function with explicit lifetimes?

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.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

    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.

(Playground)

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading