What subtyping and variance means in Rust
The Nomicon is not a fully polished document. Right now, 5 of the most recent 10 issues in that repo specifically deal with subtyping or variance based on their title alone. The concepts in the Nomicon can require substantial effort, but the information is generally there.
First off, check out some initial paragraphs (emphasis mine):
Subtyping in Rust is a bit different from subtyping in other languages. This makes it harder to give simple examples, which is a problem since subtyping, and especially variance, are already hard to understand properly.
To keep things simple, this section will consider a small extension to the Rust language that adds a new and simpler subtyping relationship. After establishing concepts and issues under this simpler system, we will then relate it back to how subtyping actually occurs in Rust.
It then goes on to show some trait-based code. Reiterating the point, this code is not Rust code anymore; traits do not form subtypes in Rust!
Later on, there's this quote:
First and foremost, subtyping references based on their lifetimes is the entire point of subtyping in Rust. The only reason we have subtyping is so we can pass long-lived things where short-lived things are expected.
Rust's notion of subtyping only applies to lifetimes.
What's an example of subtyping and variance?
Variant lifetimes
Here's an example of subtyping and variance of lifetimes at work inside of a Box
.
A failing case
fn smaller<'a>(v: Box<&'a i32>) {
bigger(v)
}
fn bigger(v: Box<&'static i32>) {}
error[E0308]: mismatched types
--> src/lib.rs:2:12
|
2 | bigger(v)
| ^ lifetime mismatch
|
= note: expected type `std::boxed::Box<&'static i32>`
found type `std::boxed::Box<&'a i32>`
note: the lifetime 'a as defined on the function body at 1:12...
--> src/lib.rs:1:12
|
1 | fn smaller<'a>(v: Box<&'a i32>) {
| ^^
= note: ...does not necessarily outlive the static lifetime
A working case
fn smaller<'a>(v: Box<&'a i32>) {}
fn bigger(v: Box<&'static i32>) {
smaller(v)
}
Invariant lifetimes
Here's a case that works:
struct S<'a>(&'a i32);
fn smaller<'a>(_v: &S<'a>, _x: &'a i32) {}
fn bigger(v: &S<'static>) {
let x: i32 = 1;
smaller(v, &x);
}
The same code with all the references changed to mutable references will fail because mutable references are invariant:
struct S<'a>(&'a mut i32);
fn smaller<'a>(_v: &mut S<'a>, _x: &'a mut i32) {}
fn bigger(v: &mut S<'static>) {
let mut x: i32 = 1;
smaller(v, &mut x);
}
error[E0597]: `x` does not live long enough
--> src/lib.rs:7:16
|
7 | smaller(v, &mut x);
| -----------^^^^^^-
| | |
| | borrowed value does not live long enough
| argument requires that `x` is borrowed for `'static`
8 | }
| - `x` dropped here while still borrowed
Addressing specific points
B
is clearly a "subtype" of A
It is not.
Box
is covariant over its input
It is, where covariance is only applicable to lifetimes.
I don't know why it doesn't work or why it won't do any type coercion.
This is covered by Why doesn't Rust support trait object upcasting?
Why would they consider Box<T>
to be covariant
Because it is, for the things in Rust to which variance is applied.
See also