Variable is concider borrowed even all reference have gone out of scope

play link

struct Foo<'a> {
    name: &'a mut String,
}

impl<'a> Foo<'a> {
    fn get_name(&'a mut self) -> &'a str 
    {
        self.name
    }
}

fn main() {
    let mut name = "foo".to_string();
    let mut foo = Foo { name: &mut name };
    {
        let r = foo.get_name();
        println!("{}", r);
    }

    drop(foo);
}
error[E0505]: cannot move out of `foo` because it is borrowed
  --> src/bin/self.rs:20:10
   |
16 |         let r = foo.get_name();
   |                 -------------- borrow of `foo` occurs here
...
20 |     drop(foo);
   |          ^^^
   |          |
   |          move out of `foo` occurs here
   |          borrow later used here

In above code, the reference should no longer exist after the inner block. But from the compiler message, we can see that the foo is still in borrowed state. Strangely, the compiler also consider the drop(foo) as last use of the borrow.

Remove the 'a from &mut self. When the lifetime is also annotated on the struct itself, it means "borrow this forever".

4 Likes

Is there any document about this this? Plus, if I change &'a mut self' to &'a self, it works fine.

Because Foo borrows name for the lifetime 'a, the declaration

tells the compiler that self needs to be exclusively borrowed for the same period. This results in a contradiction: the Foo instance cannot be used (or dropped) until 'a is over, but 'a can't end until after the instance has been dropped. The net result is that 'a never ends and Foo cannot ever be used again.

2 Likes

When you require 'a on &mut self or &self, you must provide an &'a Foo<'a> or &'a mut Foo<'a> to call it, where the two lifetimes are equal.

For immutable references, an &'short Foo<'long> is convertible to &'short Foo<'short>, so you are able to borrow the foo for the duration 'short. However, the same conversion does not apply if the outer reference is mutable, so you have to instead create an &'long mut Foo<'long> to get the two lifetimes to be equal, but now you are borrowing it for too long.

Note that the problem here is that the lifetime on the outer reference is forced to be equal to whatever lifetime is annotated on the Foo in your variable, but a variable cannot be annotated with a lifetime that ends before the struct itself is destroyed.

1 Like

Another way to see it: A &'a mut Foo<'b> means that the reference borrows foo for the duration 'a, and the foo borrows name for the duration 'b. You are requiring that 'a = 'b, i.e. that the duration in which get_name borrows foo is equal to the duration in which foo borrows name.

Since name is still borrowed by foo at the drop call, get_name must also still borrow foo at that point.

2 Likes

I'm still confused.
Suppose the name's lifetime is 'a, Foo's lifetime is 's, we can only construct a Foo object when 'a > 's, right?

Then if &'a mut Foo means Foo is borrowed for 'a, that would means Foo is borrowed longer than it's own lifetime 's. Shouldn't that be rejected by Rust's borrow checker?

Not 'a > 's, but 'a >= 's. They can be both 'static, for example.

Again, it's not "longer", but "at least for" - i.e. &'a mut Foo<'s> is well-formed, if 's >= 'a. Together, these two conditions give 's == 'a - the constraint which is theoretically possible, so it is not rejected, but it leads to consequences which are.

3 Likes

If you struct had a destructor, then they would be strict inequalities, but when there's no destructor, it's ok if they are equal.

1 Like

That explains for &'a mut Foo<'s>. But why &'a Foo<'s> gives the different result?
If I change the self to be immutably borrowed, i.e.

    fn get_name(&'a  self) -> &'a str { self.name }

, the drop(foo) should give the same error as foo is immutably borrowed for 'a , right? But it turns out after removing the mut, the compiler accept this code.

It's because in this case, you can actually call it with an &'a Foo<'b>. This is because Foo is covariant, which allows the compiler to insert a downcast from &'a Foo<'b> to &'a Foo<'a>, where 'a is some sufficiently short lifetime.

1 Like

When does this downcast happen? Before or after the compiler decide that 'a == 's?

The call to get_name desugars to the following:

let foo_ref = &foo;
let r = Foo::get_name(foo_ref);

Here, let's say foo has type Foo<'a>. Then the type of foo_ref is deduced to be &'b Foo<'c> for some lifetimes 'b and 'c to be resolved by the borrow checker later. It computes the following equations:

  1. We must have 'c ≤ 'a since the downcast cannot yield a smaller lifetime.
  2. We must have 'b = 'c since get_name requires it.

The borrow checker then solves the equations and finds a solution where 'b and 'c are very small, containing just the call to get_name, and 'a is larger.

On the other hand, when the reference is a mutable reference, the equations are slightly different:

  1. We must have 'c = 'a since the lifetime behind a mutable reference cannot be shortened.
  2. We must have 'b = 'c since get_name requires it.

But since 'a is the duration in which name is borrowed, you get the result we saw before. There's no solution where 'b is smaller than 'a.

1 Like