After I read the subtyping chapter of the Nomicon, I couldn't wrap my head around covariance of a type parameter. Especially for the Box<T>
type, which is described as: T is covariant
.
However, if I write this code:
trait A {}
trait B: A {}
struct C;
impl A for C {}
impl B for C {}
fn foo(v: Box<A>) {}
fn main() {
let c = C;
let b: Box<B> = Box::new(c);
foo(b);
}
error[E0308]: mismatched types
--> src/main.rs:13:9
|
13 | foo(b);
| ^ expected trait `A`, found trait `B`
|
= note: expected type `std::boxed::Box<(dyn A + 'static)>`
found type `std::boxed::Box<dyn B>`
B
is clearly a "subtype" of A
and Box
is covariant over its input. I don't know why it doesn't work or why it won't do any type coercion. Why would they consider Box<T>
to be covariant where the only use cases are invariants?
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):
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:
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
A working case
Invariant lifetimes
Here's a case that works:
The same code with all the references changed to mutable references will fail because mutable references are invariant:
Addressing specific points
It is not.
It is, where covariance is only applicable to lifetimes.
This is covered by Why doesn't Rust support trait object upcasting?
Because it is, for the things in Rust to which variance is applied.
See also