Reputation: 9705
struct Foo;
impl Foo {
fn foo(&mut self) -> Result<&i32, String> {
match self.bar() {
Some(d) => Ok(d),
None => {
if self.check() {
Err(String::from("Error type 1"))
} else {
Err(String::from("Error type 2"))
}
}
}
}
fn bar(&mut self) -> Option<&i32> {
todo!()
}
fn check(&self) -> bool {
todo!()
}
}
Foo::foo
function:
self.bar()
requires mutable borrow.self.check()
requires immutable borrow.The above code fails to compile. The lifetime checker complains about the two borrow (mutable and immutable).
error[E0502]: cannot borrow `*self` as immutable because it is also borrowed as mutable
--> src/lib.rs:8:20
|
4 | fn foo(&mut self) -> Result<&i32, String> {
| - let's call the lifetime of this reference `'1`
5 | match self.bar() {
| ---------- mutable borrow occurs here
6 | Some(d) => Ok(d),
| ----- returning this value requires that `*self` is borrowed for `'1`
7 | None => {
8 | if self.check() {
| ^^^^^^^^^^^^ immutable borrow occurs here
Why the compiler prevent me to compile this code?
Clearly I am missing something here, but...
The immutable borrow (i.e. self.check()
) only happens in the None
branch. The previous mutable borrow (i.e. self.bar()
) should not bring any impact there, should it?
The same code might be written:
if let Some(d) = self.bar() {
return Ok(d);
}
// What lifetime of `self.bar` is needed at this point?
if self.check() {
// ...
} else {
// ...
}
How can I solve this problem?
Please note:
self.check()
) before the match
. Merely because of performances: self.check
might be expensive and in the "cold" path. If self.bar()
returns Some
I want to return as soon as possible from the function.RefCell
).&'a i32
represent an example returned "data" that requires lifetime. In my real code you can imagine I have a complex object which holds some references (and thus require lifetime). E.g., struct ComplexObject<'a>
.Upvotes: 2
Views: 174
Reputation: 472
The problem is that in Some(d) => Ok(d)
you're still holding a reference &i32
with the same lifetime as &mut self
of the self.bar()
call. Lifetime elisions hide this, but the code would desugar into something like this:
fn bar<'a>(&'a mut self) -> Option<&'a i32> {
todo!()
}
The self.bar()
result has lifetime 'a
, so does d
in Some(d)
. Since you're returning d
from the function, self.bar()
needs to stay mutably borrowed until the end of the function. This makes self.check()
impossible to be borrowed.
To better visualize the issue:
use std::rc::Rc;
struct Foo;
impl Foo {
fn foo<'b>(&'b mut self) -> Result<&'b i32, String> {
match self.bar() { // --+- "&'a mut self" borrowed
Some(d) => Ok(d), // --+- "'a" becomes "'b"
None => { // |
if self.check() { // --+- "&'c self" borrow failed attempt
Err(String::from("Error type 1"))
} else { // |
Err(String::from("Error type 2"))
} // |
} // |
} // |
} // --+- "&'a mut self" valid until here
fn bar<'a>(&'a mut self) -> Option<&'a i32> {
todo!()
}
fn check<'c>(&'c self) -> bool {
todo!()
}
}
The return value of self.foo
is &'b i32
, which needs the same lifetime as the return value of bar
, so 'a
needs to live at least as long as 'b
, thus &'a mut self
borrowed in self.bar
stays borrowed until the end of self.foo
.
There's three options I'd consider:
bar
to be Option<i32>
if moving is an option:struct Foo;
impl Foo {
fn foo(&mut self) -> Result<i32, String> {
match self.bar() {
Some(d) => Ok(d),
None => {
if self.check() {
Err(String::from("Error type 1"))
} else {
Err(String::from("Error type 2"))
}
}
}
}
fn bar(&mut self) -> Option<i32> {
todo!()
}
fn check(&self) -> bool {
todo!()
}
}
self.bar()
into two separate functions, one that does the mutable operation and moves the ownership from the function to the callee, and one that returns Option<&i32>
but borrows immutably:struct Foo;
impl Foo {
fn foo(&mut self) -> Result<&i32, String> {
let bar = self.bar_mut();
match self.bar(bar) {
Some(d) => Ok(d),
None => {
if self.check() {
Err(String::from("Error type 1"))
} else {
Err(String::from("Error type 2"))
}
}
}
}
fn bar_mut(&mut self) -> Option<i32> {
todo!()
}
fn bar(&self, bar: Option<i32>) -> Option<&i32> {
todo!()
}
fn check(&self) -> bool {
todo!()
}
}
Rc
for multiple ownership if you cannot move the object at all:use std::rc::Rc;
struct Foo;
impl Foo {
fn foo(&mut self) -> Result<Rc<i32>, String> {
match self.bar() {
Some(d) => Ok(d),
None => {
if self.check() {
Err(String::from("Error type 1"))
} else {
Err(String::from("Error type 2"))
}
}
}
}
fn bar(&mut self) -> Option<Rc<i32>> {
// Rc::clone the value here
todo!()
}
fn check(&self) -> bool {
todo!()
}
}
Upvotes: 1