This is a minimal reproducible error that is taken from an interpreter I'm writing. As I understand, I should be able to return a reference to a field of a struct in a RefCell, since the RefCell has a sufficient lifetime. However, the compiler is telling me I cannot return a reference to a value owned by the current function, which is baffling to me frankly.
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
#[derive(Debug)]
enum Value {
Number,
String,
}
struct Object {
pub properties: HashMap<String, Value>,
}
impl Object {
pub fn get_property(&mut self, name: &str) -> Option<&mut Value> {
self.properties.get_mut(name)
}
}
fn get_property(global_object_rcc: Rc<RefCell<Object>>, name: &str) -> Option<&mut Value> {
// Rust cannot verify that this Rc isn't the last Rc that just got moved into this function?
global_object_rcc.borrow_mut().get_property(name)
}
fn main() {
// Construct global object
let mut global_object = Object {
properties: HashMap::new(),
};
// Give it a property
global_object
.properties
.insert("Test".to_owned(), Value::Number);
// Put it in a Rc<RefCell> (rcc) for sharing
let global_object_rcc = Rc::new(RefCell::new(global_object));
// Get a reference to its property, should be valid because the reference only needs to live
// as long as the global_object
let property = get_property(global_object_rcc, "Test");
dbg!(&property);
}
Here is the error message I get:
error[E0515]: cannot return value referencing temporary value
--> src\main.rs:23:5
|
23 | global_object_rcc.borrow_mut().get_property(name)
| ------------------------------^^^^^^^^^^^^^^^^^^^
| |
| returns a value referencing data owned by the current function
| temporary value created here
This can't work. The borrow_mut()
on the RefCell
returns a RefMut
, which manages the mutable borrow and ensures there are no other while it's alive. The call to get_property
then borrows the RefMut
(implicitly via a deref and &mut self
) and returns a reference (&mut Value
) that has the same lifetime as the method's receiver (the &mut self
). So the lifetime of &mut Value
depends on the RefMut
being alive; but it's destroyed when get_property
returns, invalidating the reference.
The whole point of a RefCell
(any Cell
for that matter) is that borrows can't "escape". You can either try taking a closure which gets called with a &mut Value
; or you can return the RefMut
to the caller, with the downside that your type can't rule out that the caller holds on to it, preventing future borrows.
When calling .borrow_mut()
, a temporary borrowed reference is returned, which can then be dereferenced to mutate the internal data. The reference you get will live from the time borrow_mut()
returns until .get_property(name)
returns, which isn't long enough for a reference to live past the end of the function.
This is different from the lifetime of Rc<RefCell<Object>>
, which is actually moved into the called function and will be dropped once the function returns (however Rc will only decrement the refcount, and only drop the internal data if the refcount is 0). A reference's lifetime cannot be linked to the lifetime of the internal data within an Rc<>
since that lifetime is not known until runtime, based on the refcount.
If the .borrow_mut()
call occurred outside of fn get_property()
, and you passed in a &mut Object
to fn get_property()
, such that it returns Option<&mut Value
, you could then use a lifetime variable to link the input reference lifetime to the output reference lifetime and that would get around the compiler error:
fn get_property<'a>(global_object_rcc: &'a mut Object, name: &str) -> Option<&'a mut Value> { ... }
but that's probably not what you want to do from the looks of this example.
It would probably be better to make functions that mutate the data as needed, such that you only have the borrowed reference returned by .borrow_mut()
for as short a time as possible (only long enough to mutate the data within a function, and return when you not longer need the reference). Holding on to a .borrow_mut()
reference for too long can cause a panic!() if you attempt to borrow the reference more than once.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With