-1

Assume following code

pub struct Universe {
    components: Rc<RefCell<Vec<Component>>>,
    active_component: Rc<RefCell<Option<usize>>>,
}

I would like to introduce a convenience method that returns a mutable reference to the active component, e.g.

fn get_active_component(&mut self) -> Option<&mut Component> {
    if let Some(active_component_idx) = self.active_component.borrow().as_ref() {
        let i = *active_component_idx;
        return self.components.borrow_mut().get_mut(i);
    }

    Option::None
}

which results in error

145 |             return self.components.borrow_mut().get_mut(i);
    |                    ----------------------------^^^^^^^^^^^
    |                    |
    |                    returns a reference to data owned by the current function
    |                    temporary value created here

I do understand the error. The borrow_mut() creates a temporary variable which goes out of scope after the function returns. But I have absolutely no idea how you would realize such a method in rust apart from always inlining the code.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Marco Schulte
  • 392
  • 3
  • 10
  • Are all the `Rc>` wrappers needed? Especially the one holding just `Option` seems suspect. – Thomas Mar 08 '22 at 12:13
  • It's hard to answer your question because it doesn't include a [MRE]. We can't tell what crates (and their versions), types, traits, fields, etc. are present in the code. It would make it easier for us to help you if you try to reproduce your error on the [Rust Playground](https://play.rust-lang.org) if possible, otherwise in a brand new Cargo project, then [edit] your question to include the additional info. There are [Rust-specific MRE tips](//stackoverflow.com/tags/rust/info) you can use to reduce your original code for posting here. Thanks! – Shepmaster Mar 08 '22 at 15:18

1 Answers1

0

The standard way would be to mimic what RefCell does -- return a proxy struct wrapping the RefMut from .borrow_mut() and containing the vector index, implementing Deref and DerefMut.

pub struct ComponentHandle<'a> {
    vecref: RefMut<'a, Vec<Component>>,
    index: usize,
}

impl Deref for ComponentHandle<'_> {
    type Target = Component;
    
    fn deref(&self) -> &Component {
        // SAFETY: We already verified the index is valid, RefCell won't
        // dispense another mutable reference while we hold the RefMut, and we
        // don't modify the vector's length, so we know this index is valid.
        unsafe { self.vecref.get_unchecked(self.index) }
    }
}

impl DerefMut for ComponentHandle<'_> {
    fn deref_mut(&mut self) -> &mut Component {
        // SAFETY: We already verified the index is valid, RefCell won't
        // dispense another mutable reference while we hold the RefMut, and we
        // don't modify the vector's length, so we know this index is valid.
        unsafe { self.vecref.get_unchecked_mut(self.index) }
    }
}

impl Universe {
    fn get_active_component(&mut self) -> Option<ComponentHandle<'_>> {
        if let Some(active_component_idx) = self.active_component.borrow().as_ref() {
            let vecref = self.components.borrow_mut();
            let index = *active_component_idx;
            
            if index < vecref.len() {
                return Some(ComponentHandle { vecref, index });
            }
        }
        
        None
    }
}

Alternatively, this function could accept a closure to invoke, passing it the bare reference. This is simpler to code, though less idiomatic:

fn get_active_component<F>(&mut self, f: F)
    where F: FnOnce(Option<&mut Component>)
{
    if let Some(active_component_idx) = self.active_component.borrow().as_ref() {
        let i = *active_component_idx;
        f(self.components.borrow_mut().get_mut(i));
    } else {
        f(None);
    }
}
cdhowie
  • 158,093
  • 24
  • 286
  • 300
  • Thanks a lot for your answer! So the proxy object would solve the problem, because the ownership would be transferred into the struct? – Marco Schulte Mar 08 '22 at 12:58
  • @MarcoSchulte Correct, the returned struct would own the borrow on the `RefCell`. I've updated my answer with an example of that approach. – cdhowie Mar 08 '22 at 13:03
  • https://crates.io/crates/ouroboros – Shepmaster Mar 08 '22 at 15:21
  • @Shepmaster ... okay? – cdhowie Mar 08 '22 at 15:24
  • That's how you avoid writing the boilerplate for `ComponentHandle`. – Shepmaster Mar 08 '22 at 15:26
  • Looks like the amount of boilerplate is about the same, really, if you factor in the `_builder` field, and then you still need the trait impls. I could see it perhaps being useful to store an `&mut Component` to avoid the unsafe blocks. – cdhowie Mar 08 '22 at 15:29
  • Or is the idea that you would ref the wrapped Vec on Universe itself? Seems like that would cause RefCell borrow panics at runtime, though. – cdhowie Mar 08 '22 at 15:31
  • Thank you all a lot for the clarifications and ideas! – Marco Schulte Mar 09 '22 at 22:05
  • One last question, for clarification: I guess the problem in this example is the fact that `self.components.borrow_mut()` returns a `RefMut`, which is owned by the function. Therefore calling `get_mut` borrows from this value and becomes invalid when the function terminates. I would assume this wouldn't be the case, if `self.components.borrow_mut()` would return a reference? In that case no temporary value would be created and bound to the function context, instead a borrow from a borrow bound to a value owned by self is created, so I could move ownership out of the function to the caller? – Marco Schulte Mar 09 '22 at 22:11
  • @MarcoSchulte Right, but `borrow_mut()` can't return a reference due to the constraints of `RefCell`, which implements borrow checking at runtime instead of compile time. To be able to tell if there is an outstanding borrow on the cell's value, it needs to know when any outstanding borrow ends. The way it does that is to return a proxy value that implements the [`Drop` trait](https://doc.rust-lang.org/std/ops/trait.Drop.html). – cdhowie Mar 09 '22 at 22:30