Understanding when and why temporary is dropped

The code below (and in Playground)

    let v = vec![3i32, 2i32].iter();
    println!("{}", v.sum::<i32>());

Errors due to a temporary being dropped.

I expected this would convert to something close to (but the last example contradicts it):

let v = {
       let _tmp =vec![3i32, 2i32];
       <[_]>::iter(&_tmp[..])
  }; // underlying data is dropped after we exit block expression

But if that were the case, should happen the same here?

let f = move |a| a;
let v = f(&6);
println!("{v}")

But this runs fine.

So, what rule is being applied in terms of drop scope, in the first case? And in the second?

here, &6 has 'static lifetime, it's not a temporary value.

Yes I was just adding that I drafted that example too quickly, my doubt persists though, wrt the 1st case.

Is it this rule for a statement?

It seems so because this works?

fn main(){
    let v = vec![0i32, 0i32].iter().sum::<i32>();
    println!("{}", v);
}

since data is available on sum now.

for the first case, your "expected" code is not completely equivalent, but serves as a good mental model.

the difference is very subtle though.

  • this alone actually compiles:

    let v = vec![1,2,3].iter();
    
  • but this does not:

    // error[E0597]: `tmp` does not live long enough
    let v = {
      let tmp = vec![1,2,3];
      let v = tmp.iter();
      v
    };
    
    // same thing:
    let v = {
      let v = vec![1,2,3].iter();
      v
    };
    
    // also same
    let v = { vec![1,2,3].iter() };
    

so why a standalone

let v = vec![1,2,3].iter();

compiles ok, if you don't use v later? it has something to do with how the borrow checker works.

EDIT:

let me re-phrase this:

in let v = vec![1,2,3].iter();, v does borrow the temporary vector, but the temoporary value is dropped at the end of the whole (let) statement, or, you can say at the semicolon.

  • if v is later being used, then the variable v "lives beyond" the semicolon, which means when the temporary value gets dropped at the semicolon, there are still loans (borrows) that are live, so the borrow checker reports a lifetime error in the original snippet.

  • if v is never used later, then v's liveness region both starts and ends at the same semicolon (it's effectively empty), thus when the temoporary value is dropped, there's no live loans, so it passes the borrow checker.

on the other hand, if you add an inner scope, let v = { vec![1,2.3].iter() };, the temporary value is dropped at the end of the scope, i.e. the closing curly brace }, NOT the semicolon, while the variable v lives until (at least) the semicolon, which needs to extend the lifetime of the borrow to the semicolon, (even if v is never used later). so the borrow checker reports a "temporary value dropped (at the closing curly brace) while being borrowed (until the semicolon)" violation.

END of EDIT


what actually works:

  • use Vec::into_iter(), which moves the Vec as opposed to borrows it.

    let v = vec![1,2,3].into_iter();
    dbg!(v.sum::<i32>());
    
  • for this particular example where the data is constant, using an array also works, thanks to static promotion:

    let v = [1,2,3].iter();
    dbg!(v.sum::<i32>());
    
2 Likes

Whenever I see a question about temporaries, I like to link the super-let post from Mara's Blog:

I find it really good.

2 Likes

In other words:

let v = vec![1,2,3].iter();

Goes to

  let v = {
  let v = vec![1,2,3]
<[_]>::iter(&v[..])
}

If they were equivalent both would fail, but only the latter one fails, so they are not equivalent.

And you explain why but I did not get it. But at a simplified level I take that the compiler does not care since it is not used afterwards.

idk if that's too far away from the original question but just for the whole picture:

There's also something called two-phase-borrows which relates very closely to temporaries.

Here the Vec lives until the end of this statement, i.e. until ";". v is created during the statement and can only be used from the moment it is created until the end of the statement, i.e. for a very short time.

Here the tmp lives until the end of the block, i.e. until "}". The outer v is created after the block ends, so this is invalid.

This works due to a special rule, "static promotion". Since "6" is a compile time constant, it is promoted to a static value, and &6 is &'static i32.

3 Likes

NLL borrow-checker makes this even more confusing, because when the result is not used, the too-short lifetime doesn't matter.

So this code is wrong (useless), but will compile:

let v = vec![3i32, 2i32].iter();

but this forces the v to be actually used for something, so then you get a real compile error:

let v = vec![3i32, 2i32].iter();
drop(v);

BTW, there is some work on making let smarter, and handle more patterns of temporaries as a special case:

2 Likes

Thanks for the edit; I agree. This is stated in Drop Scopes for variables and temporaries

  • In the case of a block expression, the scope for the block and the expression are the same scope.

Unless they meant something else. It's a good article sometimes hard to find Drop Scopes since I rarely search for destructors (seems more advanced.)

Also as expected let v = { vec![1,2,3].iter(); }; does compile.

I think this part of the reference is relevant as well:

The temporary scope of an expression is the scope that is used for the temporary variable that holds the result of that expression when used in a place context, unless it is promoted.


In a way, this seems to reduce to the simple case of a temporary, just easier to see with desugaring of the method call expression imho.

In the case of f(&5) where a value 5 is used in the place context given by the &.B But the temporary in this case it will be promoted.

So it de-sugars to static five: i32 = 5; as others seem to point out?

Whereas in the case I wrote first, it is not promoted, and so it drops at the poins you wrote.