With Rust 1.86, we got a new subtyping relationship between trait objects through Trait upcasting. Consider these traits and a struct implementing them:
trait Media {}
trait Anime: Media {}
struct Frieren;
impl Media for Frieren {}
impl Anime for Frieren {}
The subtyping relationship is dyn Anime <: dyn Media
. With this, Box<T>
is covariant in T
and so Box<dyn Anime> <: Box<dyn Media>
. An example of this:
let some_anime: Box<dyn Anime> = Box::new(Frieren);
let some_media: Box<dyn Media> = some_anime; // this works
Box is covariant as I expected, but I also expected functions to be contravariant in their argument types. Consider these examples:
fn watch_media(media: Box<dyn Media>) {
println!("Watching media");
}
fn watch_anime(anime: Box<dyn Anime>) {
println!("Watching anime");
}
fn sit_down(watcher: impl Fn(Box<dyn Anime>)) {
watcher(Box::new(Frieren));
}
With this, sit_down(watch_anime);
works, but sit_down(watch_media);
does not:
9 | fn watch_media(media: Box<dyn Media>) {
| ------------------------------------- found signature defined here
...
23 | sit_down(watch_media); // this does not work
| -------- ^^^^^^^^^^^ expected due to this
| |
| required by a bound introduced by this call
|
= note: expected function signature `fn(Box<(dyn Anime + 'static)>) -> _`
found function signature `fn(Box<(dyn Media + 'static)>) -> _`
See the complete example on rust playground.
Logically, watch_media
handles any type that implements Media
, so it should be able to handle a type that implements Anime
. But this is not allowed. I wanted to know why this isn't allowed and if it's even possible to have contravariance in Rust in terms of supertraits (not lifetimes).
I wasn't able to find any issues related to this in the rust GitHub or any discussion here. Please let me know if I missed it.
As an example of a language where this is allowed, see the same example in python.
A note on the workaround
I know there's a way to get this working by using this instead (the complete error message even hints to it)
sit_down(|anime| watch_media(anime));
This simply uses trait upcasting to get the type we need. But my question is broader: why isn't this made more implicit by making certain types contravariant (in this case, Fn
)?