Unused generic type, duplicating an existing bound, reported as causing an extra bound?

A function generic over two lifetimes (one depending on the other), and generic over two types (each restricted by one of the lifetimes), compiles well.

But, if I add another generic type parameter bound by those two lifetimes, it fails to compile - even though the type is not used at all, and (to me) it only duplicates the existing bounds (OutCollection<'out, OutType> defines bound OutType: 'out, but this exact bound already exists for that function).

Any tips, please. (nightly-only is fine - this is for a benchmarking-only scaffolding.)

pub trait OutCollection<'out, T>
where
    T: 'out,
{
}

// COMPILES
pub fn caller_no_collection<'own: 'out, 'out, OwnType: 'own, OutType: 'out>(
    own_items: Vec<OwnType>,
) {
    called_no_collection::<'_, '_, OwnType, OutType>(&own_items);
}

pub fn called_no_collection<'own: 'out, 'out, OwnType: 'own, OutType: 'out>(
    _own_items: &'own Vec<OwnType>,
) {
}

// FAILS TO COMPILE
pub fn caller_with_collection<
    'own: 'out,
    'out,
    OwnType: 'own,
    OutType: 'out,
    OutCollectionType: OutCollection<'out, OutType> + 'own,
>(
    own_items: Vec<OwnType>,
) {
    called_with_collection::<'_, '_, OwnType, OutType, OutCollectionType>(&own_items);
    //                                                                    ^^^^^^^^^^
    //                                                                    |
    //                                                                    borrowed value does not live long enough
    // argument requires that `own_items` is borrowed for `'out`
}
// - `own_items` dropped here while still borrowed

pub fn called_with_collection<
    'own: 'out,
    'out,
    OwnType: 'own,
    OutType: 'out,
    OutCollectionType: OutCollection<'out, OutType> + 'own,
>(
    _own_items: &'own Vec<OwnType>,
) {
}

Or, get it from https://github.com/peter-kehl/lifetime-sideffect-rs (or just lib.rs).

I'm also surprised that the error is not dropck-related (that's what I had in the related project earlier).

(The use case isn't obvious, but it's minimized from real world: cami-benches/benches/shared/lib_benches.rs at main ¡ cami-rs/cami-benches ¡ GitHub)

Thank you in advance.

The lifetime generics of functions are chosen by the caller of the function, and are always longer than the function body (so that they are valid throughout the function body).

Additionally, in caller_with_collection and called_with_collection, 'out is the parameter of a trait. That makes it invariant -- you can't coerce it to something shorter.

So in caller_with_collection, you must be calling...

// Has to be `'out` exactly  vvvv
called_with_collection::<'_, 'out, OwnType, OutType, OutCollectionType>(&own_items);
// Must be the same      ^^                                             ^

The first lifetime parameter has to be longer than 'out ('own: 'out), but it also has to the the lifetime of the reference (&own_items). You can never create a reference to a local variable that is longer than the function body -- that is a caller chosen lifetime or longer than a caller chosen lifetime -- because local variables always move or are dropped by the end of the function.

So that's the source of your lifetime error.

  • You try to call called_with_collection<'x, 'out, ..>(&'x own_items)
  • But 'x: 'out and 'out is longer than the function body, so 'x is as well
  • Thus "own_items dropped here while still borrowed"
1 Like

Thank you for explaining. While I still don't fully understand invariants etc. (even after seeing it in the Nomicon), following your explanation I minimized it - and, the result is simpler. If it fits (hopefully) in the overall picture, I'll follow up here.

"Invariant" simply means "doesn't do subtyping".

In Rust, if you have some concrete type, then you can decide whether T<'long> is a subtype of T<'short>, i.e., whether T<'long> can be accepted in place of T<'short> (covariance) or vice versa (contravariance). For example:

  • a shared reference &'_ T<'long> can always[1] be passed when a &'_ T<'short> is expected, because shared references can only be read from, not written to. Thus, shared references are covariant in their type parameter.
  • a function that only needs a &'short _ argument can always[1:1] be used where a function taking a &'long _ argument is expected (because it simply won't make use of the greater length of the lifetime, but that's fine). Thus, functions are contravariant in their argument type(s).
  • a mutable reference &'_ T<'long> can't be used in place of a &'_ T<'short>, because the latter would allow a short-living object to be written to a place where a long-living value is expected, causing unsoundness. Obviously, it doesn't work the other way around, either. So mutable references can't be either covariant or contravariant. They are invariant.

However, when a trait is parameterized by a lifetime, you won't – by the very nature of traits – know what the kind and shape of the types implementing it will be. So a trait:

  • can't be covariant, because that would be unsound if a contravariant or invariant type implemented it;
  • but they can't be contravariant, either, because that would also be unsound if a covariant or invariant type implemented it.

Consequently, traits must be invariant in order to uphold safety conditions, no matter what type implements them. That's the only correct choice.


  1. Except for references to interior mutability (shared mutability) types, i.e., UnsafeCell and those containing one, directly or indirectly – e.g. Cell, or RefCell. Those types are invariant, exactly for the same reasons as those outlined in the &mut T<'_> case. ↩ī¸Ž ↩ī¸Ž

1 Like

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.