Why mutable references still borrowed?

Hi everyone

Please consider an example as following:

struct Foo<'a> {
    x: &'a i32
}

impl<'a> Foo<'a> {
    fn get_x(&'a mut self) -> &'a i32 {
        self.x
    }
}

fn bar<'a>(mut foo: Foo<'a>) {
    let _ = foo.get_x();
}

The code not compile with error "borrowed value does not live long enough, argument requires that foo is borrowed for 'a".

As I see, the compiler could've deduced lifetimes like so: lifetime_of(foo_mut_ref) = lifetime_of(foo) = lifetime_of(bar) = 'a. With this deduction, no lifetime constraints are violated.

So, why does the compiler give this error? What's wrong with the deduction above?

Thank you.

Both &'a mut T<'a> and &'a mut self are anti-patterns and red flags; they are almost never what you want (or what you think they do).

Here, it is a correct observation that you are tying all three lifetimes together by denoting everything with 'a But think about that: it can't possibly work, because the &'a mut self annotation combined with the fact that in fn bar<'a>(...) the lifetime 'a is a generic parameter means that the caller chooses this lifetime, and thus the extent of the borrow. However, you pass foo by value, meaning that it's necessarily destroyed at the end of the function, so if 'a was chosen to be longer than the fubcfion body, you would have a dangling pointer.

1 Like

Thank you for your reply.

There is still something I am not quite clear. Consider changing the signature of get_x to borrow shared instead of mutable:

struct Foo<'a> {
    x: &'a i32
}

impl<'a> Foo<'a> {
    fn get_x(&'a self) -> &'a i32 {
        self.x
    }
}

fn bar<'a>(mut foo: Foo<'a>) {
    let _ = foo.get_x();
}

Why does the code above compile successfully? If I apply your argument, the compiler should've emit the same error?

Thanks.

That's because of the invariance of mutable references and the covariance of shared references. Very roughly, shared references' lifetime can be "shrunk" if necessary to satisfy lifetime subset relationships; the same can't be done with mutable references.

3 Likes

Let me paraphrase your answer: In function fn bar<'a>, the caller can choose 'a beyond the scope of bar at call site. However, lifetime constraints can only be satisfied if 'a == lifetime_of(bar) ==> Compiler emit an error.

Do I understand it correctly?
Do you think, in this particular case, Rust's lifetime rules is a little too strict? I mean, no reference is dangling in this program, it only invalid because of Rust's rules.

1 Like

Not really; the compiler must ensure that interfaces aren't affected by any implementations. If your code were allowed to compile, then you could modify it in a way that leads to a dangling pointer.

Anyway, the example seems contrived. What are you actually trying to do? There are likely better, easier, and more robust solutions to whatever actual problem you are having than asking for allowing dangling pointers.

Actually, I do not plan to do anything with it. I just want to understand the language better by delving into some weird cases.

Thank you a lot. :slight_smile: