Variance of dyn Fn returning lifetime bound value

I am trying to figure out why my type is not covariant but I cannot find the reason based on the variance reference.

Are there any special rules for the variance of dyn Fn compared to fn?

Here are the types I am interested in:

pub trait MyTrait {}

struct Creator1<'a>(fn() -> Box<dyn MyTrait + 'a>);
struct Creator2<'a>(Box<dyn Fn() -> Box<dyn MyTrait + 'a>>);

I would expect that both types Creator1 and Creator2 are covariant in 'a but it turns out that only Creator1 is covariant in 'a, if I do:

fn is_covariant<'long, 'short>(value: Creator2<'long>)
where
    'long: 'short,
{
    let _: Creator2<'short> = value;
}

I get a compile error while the same with Creator1 works fine.

The error is:

error: lifetime may not live long enough
  --> src/lifetimes.rs:10:12
   |
6  | fn is_covariant<'long, 'short, T>(value: Creator2<'long>)
   |                 -----  ------ lifetime `'short` defined here
   |                 |
   |                 lifetime `'long` defined here
...
10 |     let _: Creator2<'short> = value;
   |            ^^^^^^^^^^^^^^^^ type annotation requires that `'short` must outlive `'long`
   |
   = help: consider adding the following bound: `'short: 'long`
   = note: requirement occurs because of the type `Creator2<'_>`, which makes the generic argument `'_` invariant
   = note: the struct `Creator2<'a>` is invariant over the parameter `'a`
   = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance

Parameters in traits (i.e. the 'a in Trait<'a>) are always considered to be invariant, and Fn is no exception. Meanwhile with fn the normal covariant and contravariant rules apply.

As an aside, Box<dyn Fn() -> Box<dyn MyTrait + 'a>> is implicitly the same as Box<dyn Fn() -> Box<dyn MyTrait + 'a> + 'a>, so 'a is also present in a covariant position, making the whole type invariant anyway even if trait parameters could be contravariant. You should specify Box<dyn Fn() -> Box<dyn MyTrait + 'static>> to avoid this (but as already said it won't make a difference)

2 Likes