Hello!
I've been watching jonhoo's Crust of Rust for Subtyping and Variance and I didn't get one thing.
I don't understand why &mut T is invariant and not contravariant over T. Even the example I came up with works:
fn foo<'a>(a: &mut &'a str, b: &'a str) {
*a = b;
}
fn check_static(_s: &'static str){}
fn main() {
let a = "abc".to_string();
let b:&'static str = "abc";
check_static(b);
let r: &mut &str = &mut a.as_str();
// r should be &mut &'a str
// b should be &'static str
//
// since &'a mut T is invatiant over T &'a str should equal to &'static str
// for this function to be callable. I don't understand why I can still call this.
foo(r, b);
}
Your example follows the sub-typing and variance rules exactly;
r is &mut &str. It is passed to an a of required type &mut &'a str. Since &mut T is invariant over T, compiler concludes that r must in-fact be &mut &'a str (making T of type &'a str).
b is of type &'static str. It is passed to a b of required type &'a str. Now, 'static is a subtype of any 'a. Hence, compiler can pass a &'static str where a &'a str is expected.
AFAIK it's not a matter of variance here. Here, the compiler checks if b has a lifetime that is compatible with *a. Since 'static is compatible with any 'a (compatible in the sense of sub-typing, 'static is a subtype of any 'a), the compiler allows it.
Here's an example I wrote for another recent thread where memory unsafety is demonstrated when forcing a &mut T to be either covariant or contravariant over T (either expanding or contracting the lifetime underneath the &mut).
(Edit: Then I added more that's not entirely correct, but I have to leave for the moment, so I'll just remove it for now.)
Nothing about b's type is invariant -- the lifetime is covariant. And in fact, any time you're making an assignment to *a, you must have gotten ahold of a &'lt str, and those are covariant over their lifetime. So in this case, the &'static can shrink to a &'a.
Here, the type of b is invariant in the inner lifetime -- the 'static. You can't do this for example:
let local = String::new();
*b = &local; // would require 'static to be covariant (contract)
// or &'local to be contravariant (expand)
Because the &local is covariant and the 'static is invariant. However, because shared references implement Copy, you can copy the &'static str out from behind the &mut of b. Once it's copied out, you have a &str type again, and it's covariant. (_c more directly illustrates the ability to do the copy. Note that &mut references are notCopy.)