I have a problem with self
borrowing in a match
expression:
fn add_once(&'t mut self, p_name: &'t str) -> Box<Element> {
match self.get(p_name) {
Some(x) => Box::new(*x),
None => self.add(p_name),
}
}
The signature of the get()
and add()
functions are:
fn get(&self, p_name: &str) -> Option<&Element>
fn add(&'t mut self, p_name: &'t str) -> Box<Element>
The compiler refuses this code:
error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable
--> src/main.rs:38:21
|
36 | match self.get(p_name) {
| ---- immutable borrow occurs here
37 | Some(x) => Box::new(*x),
38 | None => self.add(p_name),
| ^^^^ mutable borrow occurs here
39 | }
40 | }
| - immutable borrow ends here
I get that, but I don't see how I can rewrite the match
expression.
In a related question, it was solved by having the match
return a value and then calling the function. However, that doesn't work here because the meaning of the conditional is not to choose a value but selectively execute an action.
The full code sample is below:
struct Element<'e> {
name: &'e str,
}
impl<'e> Element<'e> {
fn new(p_name: &str) -> Element {
Element { name: p_name }
}
}
struct Top<'t> {
list: Vec<Element<'t>>,
}
impl<'t> Top<'t> {
fn new() -> Top<'t> {
Top { list: vec![] }
}
fn get(&self, p_name: &str) -> Option<&Element> {
for element in self.list.iter() {
if element.name == p_name {
return Some(element);
}
}
None
}
fn add(&'t mut self, p_name: &'t str) -> Box<Element> {
let new_element = Box::new(Element::new(p_name));
self.list.push(*new_element);
return new_element;
}
fn add_once(&'t mut self, p_name: &'t str) -> Box<Element> {
match self.get(p_name) {
Some(x) => Box::new(*x),
None => self.add(p_name),
}
}
}
fn main() {
let mut t = Top::new();
let plop1 = t.add_once("plop1");
let plop2 = t.add_once("plop1");
}
Let's fix the design issues first. The main issue is lifetime conflation:
You assert that
self
should live at least as long as't
, which is itself the lifetime bound of the references it will contain. It is not actually what you need,self
should live less than't
to guarantee that any&'t
is still alive and kicking untilself
dies.If we change this, we can painlessly return references to
Element
:Note that the lifetime
'a
of the reference toElement
is different (and actually will be shorter) than the lifetime't
of its contained reference.With that out of the way, this should fix the functions:
And
position
can be reused forget
:I would expect the following to work in a perfect world:
However, I recall a discussion in which it was determined that the borrow-checker was not lax enough: the scope of the borrow induced by
self.get
is computed to be the entirematch
expression even though in theNone
branch the temporary cannot be accessed.This should be fixed once "Non-Lexical Lifetimes" are incorporated in Rust, which is a work in progress.