Coroutine Lifetimes and Hazards
Frames are heap allocations. Parameters are copied or moved in. Lambdas with by-reference captures are the classic trap.
The frame owns more than you think
The frame stores: the coroutine's parameters (copied or moved at call time โ by-value parameters are moved into the frame; references stored as references still point outside and can dangle), all locals live across suspensions, and the promise. Frame allocation is normally a heap operator new โ unless the compiler proves the lifetime is scoped and elides it (HALO). Treat every coroutine call as an allocation unless measured otherwise.
The by-reference lambda trap
auto makeGen() {
int state = 0;
return [&state]() -> Generator<int> { // WRONG: state dies with makeGen
while (state < 3) co_yield state++;
}();
}
The lambda captured state by reference; the generator outlives it. First resume reads a dead variable. Fix: capture by value ([state]) or pass state as a by-value coroutine parameter (parameters live inside the frame โ the safe home).
Predictable lifetime rules
- Coroutine parameters are safe inside the frame if passed by value/move.
- Anything referenced (parameters by &, captures by &) must provably outlive the generator โ same discipline as views.
- The generator object owns the handle; moving the generator moves ownership; the moved-from one must not be resumed or destroyed twice (
std::exchangein the move, as in the lesson's code). - Destroy or let RAII destroy before the referenced environment dies.
The graded exercise: repair exactly the dangling-capture generator, then predict which of several snippets dangle.