From my understanding, variation is a type system construct, based on which the compiler could do much of the intended semantics of the whole lifetime idea just from purely a type system level, as illustrated in the example provided in nomicon: the assign
function wants a concrete same type T
for its two input arguments, while finding it not possible since &mut T
is invariant over T
. So far so good.
fn assign<T>(input: &mut T, val: T) {
*input = val;
}
fn main() {
let mut hello: &'static str = "hello";
{
let world = String::from("world");
assign(&mut hello, &world);
}
println!("{hello}"); // use after free 😿
}
But how about this snippet?
fn main() {
let mut s = String::from("outer");
let mut v = Vec::new();
v.push(&mut s);
{
let mut s = String::from("inner");
v.push(&mut s);
}
// code compiles iff this line is commented out
// what does variance have to say about this `println!`?
println!("{:?}", v);
}
I understood that by the fact &'a mut T
is covariant over 'a
, Rust could do type resolution to determine that the vector is indeed something like Vec<&'inner mut String>
, i.e. the concrete type of T
in Vec<T>
is &'inner mut String
, making the inner block compiles, but how exactly does the variant system determine that the println!
line is inappropriate? In other words, my question is that, to my understanding, variance is for type system to do type resolution, which means it’s applicable iff there are several types and we’d like to see if they could be coerced into same type, but println!
doesn’t have the other type to compare to.
Or, it might be the case that variance just isn’t the whole picture, and ultimately we’d rely on some other lifetime semantics to determine if code is valid Rust? To add on that, the whole subtype idea that variance relies on also ultimately stems from lifetime being covered by some other lifetime…
On a side note, what exactly is a memory model, and what does it mean to say that Rust currently doesn’t have one?