If let borrow stays after return even with #![feature(nll)] [duplicate]

独自空忆成欢 提交于 2019-12-11 13:35:28

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!