Variance and Subtyping
⚓ Rust 📅 2026-01-23 👤 surdeus 👁️ 1In regard to subtypes in Rust, I learned that if a composite type U<V> is invariant in V, then the compiler does not perform any subtyping on U with respect to V. However, after I did some experiments to check how the compiler performs subtyping, I found a result that could be inconsistent with the above assertion, so I would like to share it here.
Here is my code:
fn main() {
let x = 1;
// Case1: Works fine.
{
let y = &mut &&x;
let z = &mut &**y;
g(z);
let _extend_lifetime_of_y_up_to_here = y;
}
// Case 2: Does not compile.
{
let y = &mut &mut &x;
let z = &mut &mut **y;
f(z);
let _extend_lifetime_of_y_up_to_here = y;
}
}
fn f<'a>(_x: &'a mut &'a mut &'a u8) {}
fn g<'a>(_x: &'a mut &'a &'a u8) {}
and its result:
error[E0505]: cannot move out of `y` because it is borrowed
--> main.rs:17:48
|
14 | let y = &mut &mut &x;
| - binding `y` declared here
15 | let z = &mut &mut **y;
| -------- borrow of `**y` occurs here
16 | f(z);
17 | let _extend_lifetime_of_y_up_to_here = y;
| ^
| |
| move out of `y` occurs here
| borrow later used here
|
It is understandable that case 2 did not compile. First of all, if I explicitly denote the types of y and z, they should take the forms of &'1 mut &'2 mut &'3 u8 and &'4 mut &'5 mut &'3 u8 , respectively, because the third lifetime '3 must be inherited exactly considering the fact that both types are invariant in that lifetime while the first and second lifetimes need not to coincide across y and z due to the reborrows. Therefore, if I pass z to the function f, it effectively freezes any objects with the lifetime '3. More precisely, in compiler's point of view, when it sees y, it must assume that z and _x, which is the argument of f generated inside the function, are also alive as they share the same lifetime '3.
However, this logic should be applicable to case 1 as well since the above logic relies only on the fact that &mut &mut &'3 u8 is invariant in '3 which is true for &mut &&'3 u8. Neverthless, the compiler did not compilain about case 1.
What is the logic behind this? Could the compiler possibly perform subtyping on &mut &&'3 u8 with respect to '3?
4 posts - 4 participants
🏷️ Rust_feed