问题
I was working on a big file but this is a small toy example that causes the same issue. Sorry if the example itself makes no sense.
#![feature(nll)]
struct S(i32);
impl S {
fn foo(&mut self) -> Option<&i32> {
if let Some(val) = self.bar() {
return Some(val);
}
let y = &mut self.0;
None
}
fn bar(&mut self) -> Option<&i32> {
None
}
}
fn main() {
S(0).foo();
}
This doesn't pass the borrow checker:
error[E0499]: cannot borrow `self.0` as mutable more than once at a time
--> test.rs:9:17
|
6 | if let Some(val) = self.bar() {
| ---- first mutable borrow occurs here
...
9 | let y = &mut self.0;
| ^^^^^^^^^^^ second mutable borrow occurs here
|
note: first borrowed value must be valid for the anonymous lifetime #1 defined on the method body at 5:5...
--> test.rs:5:5
|
5 | / fn foo(&mut self) -> Option<&i32> {
6 | | if let Some(val) = self.bar() {
7 | | return Some(val);
8 | | }
9 | | let y = &mut self.0;
10| | None
11| | }
| |_____^
Shouldn't this be valid (even without #![feature(nll)]
) since it is returning in the if let
block? It's worth noting that if I change the if let
block to the following, it compiles fine
if self.bar().is_some() {
return self.bar();
}
回答1:
Let's look at the lifetimes in detail here. The function foo()
is desugared to
fn foo<'a>(&'a mut self) -> Option<&'a i32>
i.e. the returned value lives at most as long as self
; similarly for bar()
.
In foo()
, the line
if let Some(val) = self.bar() {
creates a borrow of self
that lives for some lifetime 'b
, and the returned reference val
also has this lifetime 'b
. Since you then return Some(val)
, the lifetime 'b
must outlive the lifetime 'a
of the self
parameter to foo()
, which is definitely longer than the runtime of foo()
. This means you can't borrow self
again at any later point in foo()
.
I think what is surprising in this example is that the borrow of self
even happens if bar()
returns None
. Intuitively, we feel that no reference is returned in this case, so we don't need a borrow. However, lifetimes in Rust are checked by the type checker, and the type checker does not understand the meaning of different values of the type. The value returned by bar()
has the type Option<&'b i32>
regardless of whether it returns None
or Some
, and the lifetime 'b
hast to be at least as long as 'a
– given the constraints, there is no other solution, so the borrow checker has to refuse this.
With non-lexical lifetimes, the compiler can introduce more flexible lifetimes that are not bound to lexical scopes and can overlap in ways that wasn't possible before. However, if there simply is no lifetime that satisfies all constraints, NLLs won't help you.
The last code snippet you give is quite different. Let's add lifetimes:
if self.bar<'b>().is_some() {
return self.bar<'c>();
}
Now we call bar()
twice, and each of these calls can have a different lifetime. Only the lifetime 'c
needs to outlive 'a
now, but the lifetime 'b
only needs to be long enough to call is_some()
on the result. The borrow with the lifetime 'c
only happens when the branch is taken, and no conflicts occur.
来源:https://stackoverflow.com/questions/53034769/if-let-borrow-stays-after-return-even-with-featurenll