Why do I need higher-ranked trait bounds to borrow from a generic `IntoIter<T>`?

Hi there. I recently came across the exact same issue as the post here and so I'm going to simplify that example for my question:

fn bar<Iter, T>(iter: Iter)
where
    Iter: IntoIterator<Item = T>,
{
    iter.into_iter().for_each(|t| {
        // do not consume anything
    });
    iter.into_iter().for_each(|t| {
        // consume T
    });
}

The author, and myself, were looking for a way to iterate over the borrowed T in the example above, without consuming the iterator so it can be used again. The solution later in that thread is to add the higher-ranked trait bound such as:

fn bar<Iter, T>(iter: Iter)
where
    Iter: IntoIterator<Item = T>,
    for<'a> &'a Iter: IntoIterator<Item = &'a T>,
{
    (&iter).into_iter().for_each(|t| {
        // do not consume anything
    });
    iter.into_iter().for_each(|t| {
        // consume T
    });
}

Prior to searching online for a solution, I attempted the following code which fails, however is written very similar to the solution:

fn bar<'a, Iter, T>(iter: Iter)
where
    Iter: IntoIterator<Item = T>,
    &'a Iter: IntoIterator<Item = &'a T>,
{
    (&iter).into_iter().for_each(|t| {
        // do not consume anything
    });
    iter.into_iter().for_each(|t| {
        // consume T
    });
}

There is one explanation of the higher-ranked trait bound in the thread above, however given that my attempt at a trait bound is almost identical to the solution, and the explanation I feel applies to both my bound and the higher-trait bound, I'm at a loss as to why my attempt fails. My questions are:

  1. What is the difference between my trait bound above, and the for higher-ranked trait bound?
  2. The compiler suggests modifying the generic declaration to T: 'a and adding + 'a to Iter's bound however I'm always told that the generic Iter or the variable iter either may not, or does not live long enough. Why is this not the case with the higher-trait bound?

In your case 'a is a generic parameter that gets defined by the caller. But you create the reference that is supposed to live for as long as 'a inside your function. So the lifetime of &iter is not chosen by the caller but by the callee. The reference's lifetime is not 'a but some anonymous lifetime that only exist inside of your function scope.

2 Likes

This is not specific to lifetimes, either; in fact nothing that you do inside the body of your function can be described by generic bounds (either lifetime or type) on the declaration of the function. Your example doesn't work for exactly the same reason as the following doesn't work:

fn foo<T: Display>() {
    // u64 is Display, yet this is a type erorr
    let value: T = 1337_u64;
}

u64 is Display, yet it can't be assigned to an arbitrary type T that is also Display. What should happen if the caller calls foo::<String>()?

The same consideration must apply to lifetimes. What if the caller of your non-compiling function calls bar<'static, _, _>(iter)? Clearly a reference to your local variable can't have the 'static lifetime…

4 Likes

Note that there are two problems with your solution:

  • the first one are the missing lifetime bounds the compiler is complaining about. I'm not sure why these are not an issue with for<'a>, but my guess is a difference in how implied bounds work. I would suggest you to not think much about this, implied lifetimes are tricky and don't come out often. Also, you can easily add a Iter: 'a and T: 'a bounds to fix this issue. After you do this the compiler will see the second issue though.

  • the second issue is that your 'a lifetime parameter is chosen by the caller, but this is not what you need, you need some specific lifetime, the one of the &iter reference you create inside your function. This means that this can't be solved by using a 'a lifetime parameter on your function. The solution is a bit unintuitive: if you're requiring the caller that &'a Iter is an IntoIterator for some unknown lifetime 'a then from its point of view it's as if it needs to be an IntoIterator for any possible lifetime 'a, which is exactly what for<'a> expresses.

2 Likes