rusttraitslifetime-scoping

Why is the lifetime of my returned impl Trait restrained to the lifetime of its input?


In trying to work out why some code of mine will not compile, i have created the following minimal test.

I am trying to write a function that receives something like an &Vec<i32> and returns somthing that can be converted into an Iterator over i32.

The output contains nothing borrowed from the input. It it my intention that the output has a longer lifetime than the input.

To my newbie eyes, it looks like this should work.

fn error_1<'a, I: IntoIterator<Item=&'a i32>>(_: I) -> impl IntoIterator<Item=i32> + 'static {
    vec![1]
}

But, when I test if the output can live longer than the input...

fn test_e1() {
    let v = vec![3];
    let a = error_1(&v);
    drop(v); // DROP v BEFORE a.. should be ok!?
}

I get this error.

error[E0505]: cannot move out of `v` because it is borrowed
 --> src/lib.rs:8:10
  |
7 |     let a = error_1(&v);
  |                     -- borrow of `v` occurs here
8 |     drop(v); // DROP v BEFORE a.. should be ok!?
  |          ^ move out of `v` occurs here
9 | }
  | - borrow might be used here, when `a` is dropped and runs the destructor for type `impl IntoIterator<Item = i32>`

Okay - So, rust is worried that a possible Implementation of IntoIterator MIGHT have borrowed "v" ?
Playground Link - broken code
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=41ef3bce0157cc23f824f20eb0651bd9

I tried experimenting with this further...
What absolutely baffles me, is why this next test compiles just fine... It seems to have the same problem, but rust is happy to compile it.

fn fine_1<'a, I: IntoIterator<Item=i32>>(_: &I) -> impl IntoIterator<Item=i32> + 'static {
    vec![1]
}

fn test_f1() {
    let v = vec![3];
    let a = fine_1(&v);
    drop(v); // DROP v BEFORE a.. should be ok!?
}

Playground link for tweaked, working code
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=7e92549aa4e741c4dd0aae289afcf9db

Could someone help me explain what is wrong with the first code?
How can i specify that the lifetime of the returned type is completely unrelated to the lifetime of the parameter?

Just for fun, another broken example.. this time returning somthing that has nothing to do with Vec.. same error.

    fn error_fn<'a, I: IntoIterator<Item=&'a i32>>(_: I) -> impl Fn() + 'static{
        || {}
    }

    fn test_fn() {
        let v = vec![3];
        let a = error_fn(&v);
        drop(v);
    }

I'm not looking to work-around this... I can re-factor the code.
The goal here is to learn... This exposes a gap in my understanding of life-times...
Something that, until very recently, i thought i had nailed :D

Its worth mentioning, that if i change the input to a concrete type, rather than trait impl.. `_: &Vec everything, once again, compiles fine.

It SEEMS to be the life-time on the associated type of the trait, that break everything... I just dont understand why !??


Solution

  • impl Trait on return type position implicitly captures generic type parameters. This means it is limited by their lifetimes. As far as I know there is no way to opt this out.

    It does not capture lifetime parameters, and so the variant without a use of 'a compiles fine.

    This issue provides some details.

    On nightly, this can be avoided by using type_alias_impl_trait, as it only captures what explicitly specified for it:

    #![feature(type_alias_impl_trait)]
    
    type Res = impl IntoIterator<Item = i32>;
    fn error_1<'a, I: IntoIterator<Item = &'a i32>>(_: I) -> Res {
        vec![1]
    }
    

    Playground.