Why &mut T is not covariant with T?

I would like a concrete example to understand why &mut T is not covariant with T.

The following code will not compile because B is not covariant with 'a:

struct A<'a> (&'a i32); //  A is covariant with 'a
struct B<'b> (&'b mut A<'a>); // B is covariant with 'b but invariant with 'a

fn f<'b>(x: B<'b, 'static>) {
   let r: B<'b, 'b> = x; // error: lifetime mismatch
   // What could go wrong here if this line above was accepted??
   }

A standard example is something like

fn foo<'a, 'b>(r: &'a mut &'b i32) {
    let x = 1;
    *r = &x;
}

would compile if &'a mut &'b i32 could be coerced into &'a mut &'local i32, where 'local is the lifetime of the variable x. But it would make a dangling pointer accessible to the caller of foo, so that’s no good.

2 Likes

The danger is that you can now use r to overwrite the borrowed A<'static> in x with an A<'b>. Then the owner of the A would still think it has a 'static reference, but it actually has a shorter-lived one.

For example:

struct A<'a> (&'a i32);
struct B<'b, 'a> (&'b mut A<'a>);

fn f<'b>(x: B<'b, 'static>, y: &'b i32) {
   let r: B<'b, 'b> = x;
   r.0.0 = y;
}

fn main() {
    let mut a0: A<'static> = A(&0);
    {
        let i = 1;
        let b = B(&mut a0);
        f(b, &i);
        // `a0` now contains `&i`.
    } // `i` is destroyed.
    dbg!(a0.0); // This is now a dangling reference!
}
3 Likes

Sometimes people have commented here that Rust is too hard to understand.

When I look at code like the above I can totally sympathise with them. Looking at that it's very hard to see any code that actually does anything. Through the line noise of angle brackets and tick marks.

Could someone explain, in a "Rust for Dummies" way what on Earth that is, what it does, and why we might need it?

I'm lost.

2 Likes

In English instead of code:

Suppose you have some references that are valid for a long-lasting scope, and some that are valid only for a shorter scope nested inside the longer one. Let's call these two scopes 'long and 'short.

It's generally okay to pass a long-lasting reference (like &'long str) to a function that expects a short-lived reference (like &'short str): If you can access some value everywhere in the longer scope, then you must be able to access it everywhere in the shorter, nested scope. Therefore, the compiler can automatically convert &'long references into &'short references (but not vice-versa!). The same is true for most types that contain references. For example, you can coerce Vec<&'long str> to Vec<&'short str>.

However, suppose you have a type that not only contains references, but provides mutable access to them. For example, &mut Vec<&'long str>>. If you could pass this to a function that takes &mut Vec<&'short str>>, that function could push a short-lived reference into your long-lived Vec, allowing a possible use-after-free. Therefore, the compiler will never convert one lifetime into another when it's inside a mutable type (including &mut references, locks like Mutex or RefCell, or any other types that provide interior mutation using UnsafeCell). This is called invariance because the lifetime never changes, or “varies.”

16 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.