Compiler can't use the shorter of two lifetimes when calling a function with a single generic lifetime parameter

While writing a library, I stumbled across an issue, which can be reproduced by the following minimal example:

struct T;

fn foo<'a>(a : impl Iterator<Item=&'a T>, b : &'a T) {
   _ = (a, b); 
}

fn bar<'a>(a : impl Iterator<Item=&'a T>) {
    let b = T;
    // Compiler complains here that &b doesn't life long enough (requires 'a).
    foo(a, &b);
}

fn main() {
    let a = vec![T, T];
    bar(a.iter());
}

This code does not compile due to the reason stated in the comment.
But I don't understand why. My understanding would be that 'a of foo is choosen to be the shorter of the two lifetimes (lifetime of b) and should compile fine.

associated types are invariant, so the lifetime in impl Iterator<Item = &'a T> cannot be shortened.

EDIT:

forget to mention, for this particular example, the solution is to manually "shorten" the lifetime:

fn bar<'a>(a: impl Iterator<Item = &'a T>, b: &'a T) {
    let b = T;
    foo(a.map(|x| x), &b);
}

your actual use case might be more complex, but the idea is similar, you must design the trait which can express variant explicitly like an reborrow() method and such.

7 Likes

Thank you, I did not know that. With that in mind it perfectly makes sense.

But I also feel like this should be part of the compiler error message. The Rust reference and Nomicon also don't mention invariance in this case.

yeah, I agree the reference should include this case, at least a short conclusion should be available.

this behavior is actually documented in the rustc development guide, but it's a little bit technical:

an intuitive reason why associated type must be invariant in general can be demonstrated with this example:

trait Foo {
	type Bar;
}

impl<'a> Foo for &'a i16 {
	type Bar = &'a ();
}

impl<'a> Foo for &'a i32 {
	type Bar = &'a mut &'a mut ();
}

impl<'a> Foo for &'a i64 {
	type Bar = fn(&'a mut ());
}

type CoV<'a> = <&'a i16 as Foo>::Bar;
type InV<'a> = <&'a i32 as Foo>::Bar;
type ContraV<'a> = <&'a i64 as Foo>::Bar;

as you can see, in a generic context T: Foo, there's no way to know the variant of <T as Foo>::Bar, so the compiler must assume invariant in all cases.

1 Like

Too bad that there isn't a marker trait for that, something like:

trait Foo {
    type Bar : Covariant;
}

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.