Let's have a struct which maintains a vector of students,
struct Class {
items: RefCell<Vec<Student>>,
}
struct Student {
name: String,
}
impl Class {
fn produce(&self) -> Vec<&Student> {
let items = self.items.borrow();
let res = items
.iter()
.filter(|s| s.name == "Ken")
.collect::<Vec<&Student>>();
res
}
}
I am filtering the students by their names while creating a new vector of which elements points to the original one. (I will perform some more operations on the list of references and students)
error[E0515]: cannot return value referencing local variable `items`
--> src/main.rs:114:9
|
110 | let res = items
| ----- `items` is borrowed here
...
114 | res
| ^^^ returns a value referencing data owned by the current function
Since I am new to Rust, I'd like to cross-check my understanding of borrowing. The items is a reference which is owned by the current function. As we're iterating through the vector via items, the iterator itself resolves the path to the Student instances, via Ref (items) -> Fat Pointer (Vec) -> finally Student Instance in memory, and puts the references into a new vector, and return the ownership to the caller. Since we return a new vector, why "returns a value referencing data owned by the current function"? Does the borrow checker track the access path to ensure that the &Student was borrowed as immutable and no one should have mutable access to it, and therefore it fails? But, what is the alternative except copying the structures? Thank you, folks! Having a hard time with the concepts.
You can't return a reference to the value within a RefCell without the cell's guard object.
Consider what would happen if your function were allowed to compile, and then someone called borrow_mut() on the RefCell. You would then have created overlapping mutable and immutable references, which is insta-UB.
You create a Ref<'_, Vec<Student>> you bind to the local items variable that is dropped at the end of Class::produce. You borrow from that Ref, not from self.items. Given that Ref is dropped at the end of the method call, you can't return references to it as these references would be dangling. You also can't return references that live longer than the Ref to the Vec<Student> instance inside your RefCell, as this reference wouldn't be tracked by the RefCell (it only tracks the existence of the Ref instance), not allowing it to do proper resource management by only allowing exclusive/mutable access XOR shared/immutable access.
The RefCell in your example is kind of superfluous, so removing it would be my primary choice (if possible), as the interior mutability pattern does not do well with returning references from things that allow scoped access to another resource like Ref or MutexGuard: Playground. Otherwise I would consider changing your API, possibly using callbacks that take &Student or &[Student] as argument to allow your users access to the data in the RefCell.
As @jofas said, it would be cleaner to remove the RefCell and let the aliasing check happen at compile time, if that works for your use-case. If you really need shared mutable references with runtime checking through RefCell, you can return a Vec of Refs from the produce method using Ref::map (playground):
Also, I do not really see a good reason for wrapping the Vec in a RefCell. You generally don't need RefCell except in very specialized circumstances. If you are merely trying to manage a collection of entities, definitely try to use regular borrowing and references (&T and &mut T) instead for manipulating the collection.