[solved] Mutable borrow of an "if let" lasts until function ends


#1

Hi Everyone,

In the following code, a borrow in “expect_a_or_b()” lasts much longer than it seems it should be. Could anyone help interpreting why? Thanks.

type Result<T> = std::result::Result<T, &'static str>;

struct Parser<'a> {
    s: &'a str,
}

impl<'a> Parser<'a> {
    fn expect_a(&'a mut self) -> Result<()> {
        if let Some(ch) = self.s.chars().nth(0) {
            if (ch == 'a') { return Ok(()) }
        }
        return Err("Not found a.")
    }

    fn expect_b(&'a mut self) -> Result<()> {
        if let Some(ch) = self.s.chars().nth(0) {
            if (ch == 'b') { return Ok(()) }
        }
        return Err("Not found b.")
    }

    fn expect_a_or_b(&'a mut self) -> Result<()> {
        if let Ok(_) = self.expect_a() {  // "self" is mutably borrowed.
            return Ok(())
        } else if let Ok(_) = self.expect_b() {  // This borrow failed.
            return Ok(())
        } else {
            return Err("Not found.")
        }
    }
}

fn main() {
}

The compiler (both 1.0 stable and 1.1 nightly) tells me that the commented line in expect_a_or_b() is a mutable borrow that lasts until the end of the function, which makes the next “else if” failed because “self” is already borrowed.

Compile-error message of nightly:

test.rs:25:31: 25:35 error: cannot borrow `*self` as mutable more than once at a time
test.rs:25         } else if let Ok(_) = self.expect_b() {
                                         ^~~~
note: in expansion of if let expansion
test.rs:25:16: 29:10 note: expansion site
note: in expansion of if let expansion
test.rs:23:9: 29:10 note: expansion site
test.rs:23:24: 23:28 note: previous borrow of `*self` occurs here; the mutable borrow prevents subsequent moves, borrows, or modification of `*self` until the borrow ends
test.rs:23         if let Ok(_) = self.expect_a() {
                                  ^~~~
note: in expansion of if let expansion
test.rs:23:9: 29:10 note: expansion site
test.rs:30:6: 30:6 note: previous borrow ends here
test.rs:22     fn expect_a_or_b(&'a mut self) -> Result<()> {
...
test.rs:30     }
               ^
error: aborting due to previous error

I also tried to make the borrowing code in braces, but it does not help. Specifically, I made “expect_a_or_b()” look like:

fn expect_a_or_b(&'a mut self) -> Result<()> {
    {
        if let Ok(_) = self.expect_a() {
            return Ok(())
        }
    }
    if let Ok(_) = self.expect_b() {
        return Ok(())
    } else {
        return Err("Not found.")
    }
}

The compiler complains the same way.


#2

expect_a takes a &'a mut self so calling it borrows self for 'a not for the statement where the method is called. You probably don’t need the 'a annotation in any of the methods and it makes them too restrictive.


#3

It’s simply because you have said that the borrow of self should last for the lifetime 'a. Removing the 'as fixes the problem. Why? Because impl<'a> Parser<'a> says that the parser has content with some lifetime 'a and should therefore be valid for (at most) as long as that content lives. If you would reuse that lifetime in a method like fn expect_a_or_b(&'a mut self) -> Result<()> you would say that the borrow of self that is made in a call to this method would also have to be valid for this lifetime. The problem is that this lifetime last for the whole method call and more, which is why things became weird. Removing the 'a from the method calls and letting the compiler plop in anonymous lifetimes instead fixes the problem. It’s equivalent to this: fn expect_a_or_b<'b>(&'b mut self) -> Result<()>.


#4

Thanks a lot for pointing out how to correct this, gkoz & ogeon! Seems I’m confusing the lifetime Parser has as a “template parameter”, i.e., 'a in Parser<'a>, with the lifetime of the Parser instance itself.

I did some search on Internet, in the hope to find some “authority” sources like a standard specification on exactly how lifetime works in Rust, but has no success. The most “official” one I can find is:
https://doc.rust-lang.org/book/lifetimes.html
But this is far from giving enough details to (at least theoretically) reason about all possible use cases we encounter (good or bad use cases). The others are all blogs or forum question answers. Given the fact that Rust has been 1.0 only for a few months, I guess so far there’s no such specification yet?


#5

I am working on more advanced documentation here: http://cglab.ca/~abeinges/blah/turpl/_book/lifetimes.html (may want to read the previous section for context first).


#6

Thanks, Gankro. The article indeed interpreted how the lifetime goes w.r.t. my case. And it is very helpful to understand how the lifetime works (much better than the original section).