The draft C defer specification defines lexical cleanup
N3328 specifies cleanup tied to an enclosing block, with reverse lexical ordering and explicit control-flow restrictions. Its treatment of return expressions and abnormal exits defines the limits of the proposed guarantee.
A resource release placed next to acquisition is easier to inspect than the same release repeated across several exits. C's proposed defer mechanism makes that relationship explicit, but the details depend on exactly which scope owns the cleanup. The WG14 document log entry of 23 December identifies N3328 as the first draft defer Technical Specification. Its semantics tie deferred content to an enclosing block and define the control-flow paths that execute it.
C was my first language, and I value source that exposes resource ownership. A language mechanism for cleanup is useful when it reduces the number of paths a reader must inspect separately. The proposed guarantee needs a precise scope, because a convenient spelling cannot establish correct ownership on its own.
The document is a draft extension
N3328 describes extensions to ISO/IEC 9899:2024. Its cover explicitly identifies a draft circulated for comments and approval. It is not a claim that defer has become part of published C23. The distinction matters for code examples, which describe the proposal's semantics and do not establish support in an ordinary C23 compiler.
The proposed syntax is defer followed by a secondary block. Reaching that statement activates content for later execution. The content runs just before leaving its enclosing block through the specified ordinary exits, including reaching the end or using return, goto, break or continue. Deferred statements that execution never reaches do not run.
This makes activation a control-flow fact. A cleanup written after an unconditional return cannot release anything, because execution never reaches it. Similarly, conditional acquisition requires the program to define when cleanup becomes active. The language mechanism can perform the deferred statement at exit, but it cannot infer whether the resource was successfully acquired.
Return values are computed before cleanup
The draft states that the return expression and conversion to the function's return type occur before deferred content executes. This separates producing a return value from modifying local state during cleanup. The following illustrative example uses the proposed syntax.
int result_before_cleanup(void)
{
int status = 7;
defer { status = 9; }
return status;
}
Under N3328's rules, this returns seven. The deferred assignment takes place after the return value has been calculated. The example is deliberately small so that the ordering is visible without involving a library resource or another function's error behavior.
For an application, this means cleanup should not be assumed to revise an already computed return code. If release can fail and that failure must determine the result, the program needs an explicit design for observing it before the result is fixed. A deferred statement still executes at the specified point, but the function's result contract must agree with that ordering.
The same reasoning applies to returning a pointer to storage whose lifetime the cleanup ends. Computing a pointer value before cleanup does not keep its referent alive afterward. The program must distinguish transferring ownership to the caller from releasing ownership locally. The proposal supplies execution ordering, while the API supplies the ownership rule.
Ordering is lexical within the block
Multiple deferred statements execute in reverse lexical order within their enclosing block. Deferred content can itself contain deferred statements, whose content runs at the end of that nested deferred region according to the same rules. The draft includes a worked example in which this ordering changes a local integer through several operations.
Reverse ordering fits a common resource relationship. If a later-acquired object depends on an earlier one, releasing the later object first can preserve that dependency during cleanup. That is an architectural use of the rule, rather than a guarantee about all resource combinations. Independent resources may have no ordering requirement, and a particular API can require a different shutdown sequence.
The lexical qualification also matters for readers familiar with similarly named mechanisms elsewhere. The correct interpretation comes from the C draft's enclosing-block and activation rules. Assuming that a shared keyword implies identical scope behavior in another language would skip the part of the specification that determines when cleanup actually happens.
I find the enclosing block a useful unit to review. It gives each activation a visible region and an exit boundary. The surrounding function can then use smaller blocks to express when a resource is no longer needed, subject to the proposal's control-flow constraints.
Jumps cannot bypass the declaration structure freely
N3328 constrains goto and switch so that they cannot jump into deferred content or jump over defer statements in the prohibited ways. It also prohibits a return that exits the deferred content itself. These restrictions make the cleanup structure part of the language's control-flow rules.
That has implications for adopting the mechanism in code with existing cleanup labels. A label structure designed around manual release may cross the new activation points. Replacing a release call with defer syntax without examining those jumps can therefore create a constraint violation. The change needs to preserve both the resource policy and the permitted control flow.
The restrictions also help explain why this is more than another spelling for a cleanup function call. A direct call executes where it appears. A deferred statement defines work associated with leaving a block, so the compiler has to account for the block's possible exits. The source language needs rules for jumps that might otherwise make activation ambiguous.
Abnormal termination has a narrower guarantee
The draft does not promise cleanup for every way a program can stop or leave a scope. It discusses nonreturning functions and termination signals separately, and gives implementation latitude outside the specified ordinary exits. Non-local jumps receive their own rules, including unspecified behavior for certain jumps involving deferred content.
This limits what an application should place inside the proposed guarantee. Cleanup that must occur after an external termination event needs another system-level mechanism or recovery contract. A scope-exit statement cannot establish that a process will execute additional instructions after every possible failure. The documented ordinary exits remain useful without claiming that broader property.
For a resource-owning function, the substantive design question is which exits the caller can observe and what state remains after them. N3328 gives ordinary block exits a specific cleanup order and evaluates return values before that cleanup. Those rules let a reader analyze the normal error paths while keeping abnormal termination and ownership transfer as explicit parts of the surrounding API contract.