Return from block "does not live long enough" without temporary

While it's in the same genre as this question, I feel my particular case is a little more surprising. Apologies if it has been asked about before, and I couldn't find it.

Here is a contrived reproducer. My original code is the same structure as this, but does a different type conversion and split point calculation (making the unwrap()s unnecessary):

let places: Vec<String> = {
    let mut places: Vec<_> = vec!["Hamptons", "Hollywood", "Costa Rica"];

    let non_h_places = places.iter().position(|item| item.chars().next().unwrap() != 'H').unwrap();
    places.drain(non_h_places..).try_fold::<Vec<_>, _, anyhow::Result<Vec<_>>>(Vec::new(), |mut vec, s| {
        vec.push(s.to_string());
        Ok(vec)
    })?
};

Playground link

Written this way, the compiler complains that the inner places does not live long enough.

In general, I understand why: the Drain iterator mutably borrows the collection without owning it, and the TryFold iterator on it consumes that. The compiler wants to aggressively clean up the collection at that point, hence the error.

The solution (which is even suggested the compiler iteslf, good work on the diagonstic messages!) is to add a temporary binding. What that really does, IIRC, is implicitly require a longer lifetime on the iterator -- because the TryFold is required to complete so its output value can live past the end of the statement. Thus, the collection is not dropped until after the last statement in the block.

In this case, however, the collection created by the TryFold is the return value of the block. I would expect the compiler to extend the lifetime it in a similar way, since it is "bound" to the stack slot reserved for the return, which will be put into an outer scope.

But this doesn't happen, apparently. Instead, I need to create a temporary just to return it immediately:

    let vec = places.drain(non_h_places..).try_fold::<Vec<_>, _, anyhow::Result<Vec<_>>>(Vec::new(), |mut vec, s| {
        vec.push(s.to_string());
        Ok(vec)
    })?;
    vec

This is not specific to inline blocks, either. Turning it into a stand-alone function has the same problem.

This behavior seems rather silly to me. What do the compiler experts think? Is this worth filing a (minor) GH issue, in the hopes it could be fixed without regressing borrow checking in most cases?

I admit that I also found this surprising. The problem is one of drop order, namely that temporaries used in the expression returned from the block are dropped after variables defined in that block.

It would be a breaking change to modify the drop order. Beyond that, the fact that they have a specific compiler help message for exactly this situation tells me that they have already thought about what the compiler should do in this case.

1 Like

Issue 69367. It was considered for the next edition but "the rule would be tricky" (possible solutions have not been adequately explored). More conversation from the meeting.

1 Like

Thanks, @quinedot and @alice! I'll consider this solved.

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.