The C defer draft gives cleanup an explicit lexical scope
Revision 3 defines the scope of a defer statement and expresses jump restrictions through that scope. The change sharpens when cleanup runs and what control flow may enter.
Attaching cleanup to a block leaves an additional question when C's jumps can move between points inside that block. The third defer committee draft addresses that question by defining a scope for each defer statement. Cleanup is specified in relation to leaving that scope as well as leaving the enclosing block. The change makes the position of the statement part of the lifetime model.
The WG14 document log dates N3688 to September 28. The revision 3 draft continues the proposed technical specification for lexical cleanup in C. It remains a draft extension to C23, with JeanHeyd Meneide as project editor. Publication of a committee paper is not a statement that ordinary C23 compilers implement it.
The new scope definition is the useful story in this revision. A defer's scope is the same as that of an identifier declared and completed immediately after its deferred body. That gives readers a lexical rule to apply to a source location. It also lets the draft state jump restrictions in terms of entering or leaving a defined region.
Why the statement needs its own scope
The June revision, N3589, described activation and cleanup chiefly through the enclosing block. It prohibited certain jumps over defer statements in that block. Revision 3 instead prohibits goto or switch from moving from outside a defer's scope to inside it. Both formulations protect cleanup activation, but the newer one names the region that activation governs.
The scope begins after the deferred body in the textual sense used by the identifier analogy. During execution, reaching the statement enters that scope without immediately running the cleanup body. A later qualifying exit runs the body just before the exit. The draft consequently separates the deferred code itself from the following region whose exits it protects.
I find that distinction useful in C because a block alone does not describe every relevant control-flow transition. A label can be earlier in the same block as the resource acquisition. A cleanup proposal needs an account of that transition that does not depend on a programmer treating every function as if it had only one path through its statements.
Entering protected code cannot bypass activation
N3688 prevents a jump into the body of a defer statement. It also prevents a jump from outside the defined scope into it. This second restriction addresses the case where control could arrive after the defer statement without having reached it normally.
Consider the following illustration of the forbidden structure. It uses the proposed syntax solely to show the constraint, and is not intended to compile as portable C23.
void example(void)
{
goto work;
defer { release_resource(); }
work:
use_resource();
}
The jump enters the region after the defer statement from outside that region. N3688 treats this as a constraint violation. The language therefore does not need an ordinary execution path where cleanup appears lexically applicable but its activation was skipped. A jump that goes around an entire nested block presents a different question because it need not enter the scope of a defer in that block. The paper includes examples that distinguish these placements instead of banning every use of a label near cleanup.
The functions in that schematic example stand for application-defined operations. The example does not claim that the draft introduces resource types or standard functions with those names. Its point is the placement of the label and the direction of the jump.
Leaving scope computes cleanup in reverse order
Revision 3 keeps reverse appearance order for multiple defer statements in an enclosing block. Nested defers follow the same rule within their own bodies and scopes. This gives the programmer an ordering rule that can preserve dependencies between resources during release.
Return evaluation also keeps its defined order. The return expression and conversion to the return type occur before the deferred bodies run. If a return expression calls a function that still needs a resource, cleanup does not release it before that call computes the return value. Conversely, changing a local in a deferred body does not replace a value already computed for return.
That rule makes cleanup distinct from rewriting the return expression. The pending exit has a result, and cleanup occurs while leaving the relevant scope. The deferred code can have ordinary side effects, but the evaluation order determines which of them the return computation can observe.
The draft continues to prohibit return, break, continue, or goto from exiting a deferred body. This protects the surrounding control transfer from cleanup that attempts to leave through a different route. A nested control structure inside cleanup must be interpreted with that boundary in mind.
Nonlocal jumps do not acquire universal unwinding
N3688 treats nonlocal jumps separately. If such a jump leaves the defer's scope before its body executes, that body is not executed. A nonlocal jump from within a deferred body that leaves it has undefined behavior. The draft also excludes jumps into the protected scope or deferred body through the specified nonlocal routes.
Its explanatory note says runtime tracking of reached defers is unnecessary under these rules. Leaving the scope through the nonlocal path forgets the deferred action, while reaching the statement again follows the ordinary scope rules. This describes the intended implementation consequence of the lexical definition without promising cleanup on arbitrary stack unwinding.
Calls to non-returning functions and termination by the listed signals likewise do not automatically run pending cleanup. An implementation can specify additional behavior, but the draft's portable guarantee has a narrower set of exits. Describing defer as something that always runs would erase those explicit limits.
I favor the lexical definition because it makes a source review more precise. The question is whether a path reaches the statement and then leaves its scope through a covered exit. The September draft supplies a named scope and jump constraints for answering that question. Its remaining work concerns a proposed language extension, but its cleanup model can already be assessed through concrete control-flow paths.