Invariant rule is broken?

To my understanding, the code, Rust Playground should not pass compiling, but it compiles, why?

use std::marker::*;

#[derive(Debug)]
struct WhatAboutThis<'a> {
    name: i32,
    _a: PhantomData<&'a WhatAboutThis<'a>>,
}

impl<'a> WhatAboutThis<'a> {
    fn x(self: &'a mut &'a Self) {} 
}

fn main() {
    // tricky can't outlive 'a
    let mut tricky = WhatAboutThis {
        name: 32,
        _a: PhantomData,
    };

    // &mut &'1 tricky
    let p = &mut &tricky;  

    // signature of x: fn x(self: &'a mut &'a Self) {} 
    //  1. &mut T is invariant at T, so '1 == 'a 
    //  2. T is &'1 tricky,  '1 should not outlive strike
    //  3. tricky can't outlive 'a, neither, because of _a: PhantomData<&'a WhatAboutThis<'a>>
    // so, '1 == 'a == scope_life_of(tricky)
    p.x();  

    // move tricky, should fail to compile because '1 is still alive
    let z = tricky;
    
    println!("{:?}", z);
}

When the move happens, &'1 tricky is ended, so '1 is dead, thus the move is successful.

As for the invariance of &'a mut Type<'a>, it means these things:

  • Type<'a> lives as long as the referene &'a mut lives
  • &'a mut lives as long as the referene Type<'a> lives
  • Type<'a> dies as long as the referene &'a mut dies
  • &'a mut dies as long as the referene Type<'a> dies

The move only invalidates the lifetime 'a,with the invariance rule upheld.

    let p = &mut &tricky;  
//               ^^^^^^^

WhatAboutThis<'w> is covariant in 'w, so the underlined portion just chooses some 'm to create a &'m WhatAboutThis<'m> that matches the lifetime of the &'m mut (which need only stick around until the call to p.x()).

To avoid the covariance (for fun and profit) during construction of the &mut & _... hmm... I don't know if there is a safe way to do so. This accomplishes it:

    fn hmm<'x, 'y>(self: &'x mut &'y mut Self) -> &'x mut &'y Self {
        let ptr: *mut &'y mut Self = self;
        let ptr = ptr as *mut &'y Self;
        unsafe { &mut *ptr }
    }

// ...

    let p = (&mut tricky).hmm();

But that doesn't really mean much as a &'a mut Self must also be created to call hmm with the necessary lifetimes in the playground, and that alone is enough to create the borrowed-forever scenario.

Generics or TAIT or something might be able to create the &'a mut &'a WhatAboutThis<'a> without borrowing it forever before the creation.

1 Like

What means create a &'m WhatAboutThis<'m> ? Is the WhatAboutThis<'m> indeed the tricky? If it is tricky, then tricky is a instance of WhatAboutThis<'m>, which holds a reference of &'m WhatAboutThis<'m>.

Seems you are talking about a 'm and 'm < 'a. Then WhatAboutThis<'m> should not be the tricky.

You can't take a &mut to a &WhatAboutThis<'_> until the &WhatAboutThis<'_> exists.

Here it is in slower motion.

Or without the intermediate reasoning:

    // Call this a WhatAboutThis<'w>
    let mut tricky = WhatAboutThis {
        name: 32,
        _a: PhantomData,
    };

    // This is a &'m WhatAboutThis<'m> ('w: 'm)
    let mut r = &tricky;
    // This is a &'m mut &'m WhatAboutThis<'m>
    let p = &mut r; 
    // Due to this call
    p.x();

    let y = r; // fails (r is borrowed forever -- for `'m`)

    // This is fine (tricky is not borrowed forever; `'w` > `'m`)
    let z = tricky;
1 Like

You can change &'a WhatAboutThis<'a> to *mut WhatAboutThis<'a> to avoid covariantion.

struct WhatAboutThis<'a> {
    name: i32,
    _a: PhantomData<*mut WhatAboutThis<'a>>,
}

Now the compiler won't compile.

error[E0505]: cannot move out of `tricky` because it is borrowed
  --> src/main.rs:38:13
   |
16 |     let tricky = WhatAboutThis {
   |         ------ binding `tricky` declared here
...
22 |     let p = &mut &tricky;  
   |                  ------- borrow of `tricky` occurs here
...
38 |     let z = tricky;
   |             ^^^^^^
   |             |
   |             move out of `tricky` occurs here
   |             borrow later used here

For more information about this error, try `rustc --explain E0505`.

So I can

impl<'a> T<'a> {
    fn method(this: &mut &'a Self) {}
}

let ref : &'any T<'short> = &'any T<'long>; // as long as T is covariant

ref.method(); // will call T::<'short>::method(&mut &'short Self)
              // not T::<'long>::method(&mut &'long Self)

Is that correct?

So long as T<'t> is covariant in 't, yes.[1]


  1. And you don't use a keyword for a variable name :wink: ↩︎

1 Like

Only covariant in 't, not the whole T<'t>?

T is covariant when all it's fields is covariant; While only covariant in 't and other fields is not will lead T to be invariant. Is that Ok?

It proved to be true, covariant at lifetime is enough. Rust Playground

Variance is a per-parameter sort of thing. &'a mut T is covariant in 'a and invariant in T for example.

T<'t> only has the one parameter so it comes down to the same thing (but I prefer to be specific due to cases like &'a mut T).

1 Like