rustborrow-checkerownershipmutable-reference

How can I return a mutable reference to a value owned by the current function


Here is my struct and I need a get_mut function that returns a mutable reference to a value owned either by ctx.scope or ctx.parent.scope recursively

pub type RuntimeContext<'c> = Rc<RefCell<Context<'c>>>;

pub struct Context<'c> {
    pub parent: Option<RuntimeContext<'c>>,
    pub scope: HashMap<String, Value>,
    pub eval: Value,
}

This is what I tried, but as expected the borrow checker complains:

error[E0515]: cannot return reference to temporary value
  --> src/lib.rs:20:13
   |
20 |             parent.borrow_mut().get_mut(key) // here it breaks
   |             -------------------^^^^^^^^^^^^^
   |             |
   |             returns a reference to data owned by the current function
   |             temporary value created here

What other way is there to achieve this?

(the parent ctx is guaranteed to outline the current ctx)

impl<'c> Context<'c> {
    pub fn get_mut(&mut self, key: &str) -> Option<&mut Value> {
        if let Some(v) = self.scope.get_mut(key) {
            Some(v)
        } else if let Some(parent) = &mut self.parent {
            parent.borrow_mut().get_mut(key) // here it breaks
        } else {
            None
        }
    }

    // ... some other methods
}

Solution

  • You can't; there simply is no way to return a &mut reference to member of an object wrapped in Rc<RefCel<>>. RefCell requires a guard object to keep track whether the object is currently borrowed. A &mut reference, however, does no such thing, making it impossible to know for the RefCell that the &mut reference exists after its guard got dropped. Which happens at the end of the function.

    It would, in general, be possible to return something that still contains the guard object, but that would of course not be compatible with the other return path where you want to return a reference from the scope member.

    There are two ways I could think of how to achieve this:

    • An enum return value that could return either a reference or a guard object
    • A context manager like closure based approach where you return nothing, but instead call a given function with your value

    The enum return value is quite difficult to get right and will become very complicated due to the multiple-layers-deep nesting of variables.

    EDIT: I don't think the enum idea would work because it would run into lifetime problems.

    So I personally would go with the second option.

    Here is how that could look like:

    use std::{cell::RefCell, collections::HashMap, rc::Rc};
    
    #[derive(Debug)]
    pub struct Value;
    
    pub type RuntimeContext<'c> = Rc<RefCell<Context<'c>>>;
    
    pub struct Context<'c> {
        pub parent: Option<RuntimeContext<'c>>,
        pub scope: HashMap<String, Value>,
        pub eval: Value,
    }
    
    impl<'c> Context<'c> {
        pub fn get_mut<R>(&mut self, key: &str, f: impl FnOnce(Option<&Value>) -> R) -> R {
            if let Some(v) = self.scope.get_mut(key) {
                f(Some(v))
            } else if let Some(parent) = &mut self.parent {
                parent.borrow_mut().get_mut(key, f) // here it breaks
            } else {
                f(None)
            }
        }
    
        // ... some other methods
    }
    
    // Example on how to use this
    pub fn do_something(context: &mut Context) {
        let y = context.get_mut("foobarkey", |value| {
            println!("Got value: {:?}", value);
            let computed_result = 42;
            computed_result
        });
    
        println!("Result: {}", y);
    }