4

tl;dr given pub fn func(&'a mut self), why is self considered "mutably borrowed" after func has run?

Given the following minimal viable example (playground)

pub struct Struct1<'a> {
    var: &'a u8,
}

impl<'a> Struct1<'a> {
    pub fn new() -> Struct1<'a> {
        return Struct1 {
            var: &33,
        }
    }
    pub fn func(&'a mut self) -> () {
        ()
    }
}

fn main() {
    let mut s1 = Struct1::new();
    s1.func();  // point 1
                // point 2
    s1.func();  // point 3
}

results in compiler error

error[E0499]: cannot borrow `s1` as mutable more than once at a time
  --> src/test12-borrow-mut-struct-twice-okay.rs:20:5
   |
18 |     s1.func();  // point 1
   |     -- first mutable borrow occurs here
19 |                 // point 2
20 |     s1.func();  // point 3
   |     ^^
   |     |
   |     second mutable borrow occurs here
   |     first borrow later used here

However, at // point 2 the s1 appears to me to not be anymore borrowed. The func is done running. What could be still be borrowing self within func!? It appears func at //point 1 has relinquished control of s1.

What is still borrowing s1 at // point 3 ?


Similar questions:

JamesThomasMoon
  • 6,169
  • 7
  • 37
  • 63
  • I know there are similar questions around this. Most of those questions focus on working around the specific problem. Whereas, I'm hoping some rust guru(s) will decisively answer the broader question. – JamesThomasMoon Sep 03 '21 at 07:27
  • 3
    It's still borrowed because *you* are telling the compiler that you're borrowing for the `'a` lifetime defined on the `impl` block, which is the `'a` lifetime associated with the structure. Your `&'a mut self` desugars to `self: &'a mut Struct<'a>`. – Masklinn Sep 03 '21 at 07:32
  • @Masklinn IMO this question is valid enough to get an answer, which would be your comment – Denys Séguret Sep 03 '21 at 07:35
  • More generally, it's not uncommon to have problems simply linked to using too restricted lifetimes and you can solve them by having different lifetimes (either declaring new ones or making them anonymous) – Denys Séguret Sep 03 '21 at 07:36
  • 1
    @DenysSéguret Ok I'll flesh it out to an answer. – Masklinn Sep 03 '21 at 07:36
  • Quite similar problem here: https://stackoverflow.com/q/67130206/263525 (not similar enough to cast a duplicate vote) – Denys Séguret Sep 03 '21 at 07:40
  • 1
    Thanks @DenysSéguret . Indeed, this problem is lurking within many SO `rust` questions. However, most of them touch on it in a convoluted way, while trying to solve something else. I wanted to distill this to it's simplest form and focus on the underlying rust concepts so I could understand how to solve all forms of it, as opposed to just copy+pasting someone's code suggestion. – JamesThomasMoon Sep 03 '21 at 07:43
  • 2
    @DenysSéguret [this one](https://stackoverflow.com/questions/64803836/rust-lifetime-problem-why-the-first-call-not-compile-error) seems like a more straightforward duplicate, but asks the question the other way around so hard to find. And of course "second mutable borrow occurs here" matches millions of issues not related with getting function prototypes wrong. – Masklinn Sep 03 '21 at 07:46
  • @Masklinn I believe [Why does this mutable borrow live beyond its scope?](https://stackoverflow.com/q/66252831/2189130) or [Mutable borrow seems to outlive its scope](https://stackoverflow.com/q/32403837/2189130) would be a more apt duplicate – kmdreko Sep 03 '21 at 14:55
  • @kmdreko indeed, excellent previous versions. – Masklinn Sep 03 '21 at 17:15

1 Answers1

10

What is still borrowing s1 at // point 3 ?

You're telling the compiler that it's still borrowed, so it's trusting you: while the compiler verifies that your lifetimes are not too short, it doesn't really care if they're so long they make things unusable.

When you write &'a mut self, the 'a is the one declared on the impl block, and thus the one defined on the struct. &'a mut self literally desugars to:

self: &'a mut Struct1<'a>

so once you've called func() the rust compiler goes "well this is borrowed for 'a which is the lifetime associated with s1 which is 'static, so this is mutably borrowed forever, good day", and thus you get "locked out" of the structure.

In fact you can see this aliasing by trying to explicitly declare an 'a on func:

    pub fn func<'a>(&'a mut self) -> () {
        ()
    }
error[E0496]: lifetime name `'a` shadows a lifetime name that is already in scope
  --> src/main.rs:11:17
   |
5  | impl<'a> Struct1<'a> {
   |      -- first declared here
...
11 |     pub fn func<'a>(&'a mut self) -> () {
   |                 ^^ lifetime `'a` already in scope

error: aborting due to previous error

So rust is telling you in no uncertain term that within the block 'a always refers to the lifetime declared on the impl block.

The solution is to just remove the 'a, that's the wrong lifetime entirely:

    pub fn func(&mut self) -> () {
        ()
    }

In that case rustc will automatically introduce a lifetime, and since the function is not actually borrowing anything that lifetime will only extend to the function call.

Masklinn
  • 34,759
  • 3
  • 38
  • 57
  • 2
    Ah! Indeed, my actual program, not the example one here, compiled when I removed the unnecessary lifetime on the struct function. Fantastic! Incredibly obvious now that you point it out. I'm a rust n00b and I _think_ I'm understanding lifetimes better (though I keep telling myself "_I think I understand rust now_" and then I don't ). Thanks much! – JamesThomasMoon Sep 03 '21 at 07:52
  • 3
    The lifetime associated with `s1` is not `'static`; it's rather a lifetime `'a` that is yet to be determined by the compiler, but that covers at least the time until `s1` goes out of scope. Which still means that any call to `func()` borrows `s1` as long as it lives, so it doesn't change anything about the reasoning you gave. – Sven Marnach Sep 03 '21 at 08:59