Nomicon variance question

Hello,
The Nomicon chapter about Layout mentions this about variance:" So a &Vec<&'static str> couldn't be used where an &Vec<&'a str> was expected"
Why does this code compile?

pub struct MyVec<T> {
    ptr: *mut T,

}

fn f2(obj:&MyVec<&'static str>) {
    f1(obj);
}

fn f1<'a>(obj:&MyVec<&'a str>) {

}

What is a good counterexample where a PhantomData would solve the problem?
Regards
Pent

1 Like

In the body of f2, the compiler is free to pick any 'a for f1 that makes the whole function compile. So it infers that 'a must be 'static because you passed f1 a &MyVec<&'static str>.

Here's a function that only compiles if MyVec<T> is covariant in T:

fn f3<'a>(dest: &mut MyVec<&'a str>, obj: MyVec<&'static str>) {
    *dest = obj;
}

The compiler doesn't have any freedom to pick what 'a is because that will be chosen by the caller. In order for the assignment to work, MyVec<&'static str> must be a subtype of MyVec<&'a str> for any lifetime 'a.

5 Likes

This is an excellent explanation (Should go in the book...)

Another way to frame it is the fact that lifetimes are also generics, and just like how the compiler can infer the generic types here:

let x = vec![1usize, 2, 3, 4];
fn foo<T>(z: Vec<T>) {}
foo(x);
//Is actually 
foo::<usize>(x) {}

We can then see that the following also applies:

fn foo<'a>(z: &'a [()]) {}

{ //<-- start of 'a
    let x = vec![(), (), ()];
    foo(&x);
    //Is actually
    foo::<'a>(&x);
} //<-- end of 'a

//So:
static FOO: &'static [u8] = &[1, 2, 3, 4];
foo(FOO);
//Is actually
foo::<'static>(FOO);
4 Likes