Clang 22 makes scope cleanup testable in C
Clang 22 implements the draft defer specification behind a flag. Its jump restrictions matter as much as its cleanup syntax.
LLVM 22.1.0, released on February 24, includes an experimental implementation of the C defer draft Technical Specification. The Clang release notes name WG14 N3734 as the implemented document and require -fdefer-ts in C mode. They explicitly warn that the specification is not ratified and its details may change. That makes this release useful for evaluating a design without treating it as a settled portability commitment.
I have thirty years with C, which was my first language. I value its relatively small language definition, but writing it well requires attention to the boundaries of defined behavior. Scope cleanup interests me because it changes what a reviewer must establish about an exit path. I would judge the proposal by the control flow it permits and the mistakes the compiler rejects, as well as by how little code a simple example needs.
Cleanup follows a lexical scope
The N3734 draft introduces _Defer followed by a statement or block. Reaching that declaration postpones the block until the enclosing scope ends. Ordinary exits through return, goto, break and continue execute applicable deferred blocks. Multiple deferred blocks run in reverse order. Return expressions are evaluated before the deferred work executes.
The following example illustrates that ordering. The declared cleanup function is an application function, so the example does not depend on a particular resource library.
void release_resource(int resource);
int work(int resource)
{
_Defer {
release_resource(resource);
}
if (resource < 0)
return -1;
return resource;
}
Compile an experiment with Clang 22 using the documented flag.
clang -std=c23 -fdefer-ts -fsyntax-only example.c
Here the cleanup executes for either return after execution reaches _Defer. A real resource API would need to define whether a negative handle can be released, and the caller should arrange the cleanup declaration accordingly. I would put the registration after successful acquisition when failure leaves nothing to release. That policy belongs to the resource contract, so a language feature cannot infer it from the spelling of the cleanup function.
The draft also limits expectations about nonlocal jumps and program termination. A longjmp that leaves the scope does not provide the ordinary cleanup guarantee, and termination through other means does not generally execute pending blocks. I would document those limitations beside an experiment involving code with unusual exits. Assuming every possible departure executes cleanup would give the experiment a stronger property than the draft provides.
The compiler rejects incompatible jumps
The release contains semantic tests for defer that make the constraints concrete. They compile in C23 mode with the experimental option and define defer as _Defer. Several tests expect an error when goto would enter a deferred statement or bypass its declaration on the way to a label. These are checks on the shape of control flow, independent of what a cleanup body does.
The tests also reject a return inside a deferred statement. A deferred block cannot break out to an enclosing loop or continue that loop. In contrast, the same test file accepts a loop wholly inside the deferred block with its own local break or continue. That distinction is useful for reviewing cleanup code with internal iteration. The restriction concerns leaving the deferred statement, while ordinary internal control flow remains possible.
Switch statements receive corresponding checks. A case label cannot provide a route into a deferred block, and dispatch cannot bypass a defer declaration to enter its scope. I would include these cases in an evaluation of code that currently organizes cleanup around labels. A syntactic replacement of an existing cleanup label would not establish that the resulting function satisfies the new restrictions.
These diagnostics are part of the practical value I see in the implementation. A proposed cleanup construct needs an account of paths that enter the region after registration, not only paths that leave it. The checked-in tests let a reader examine specific accepted and rejected functions. They also give an evaluator concrete regression cases to consult when an experiment encounters a surprising diagnostic.
Named loops address another exit problem
The same release implements C2y named loops, citing N3355. A label identifies a containing loop or switch, and break can name that target. A named continue identifies a containing loop. The proposal discusses why a numeric depth argument is fragile when another control structure or macro changes the nesting between the jump and its intended target.
That rationale matters when assessing readability. A named target records which construct should end, so adding an intervening loop does not silently change a depth count. The proposal also discusses the alternatives of repeated tests, helper functions and general jumps. Those alternatives require different changes to the surrounding function. I would compare the complete resulting code before deciding whether a new control statement improves a particular loop.
For adoption, I would evaluate named loops separately from the draft cleanup extension. The release notes list the former under C2y support and the latter as a flag-controlled draft Technical Specification. Combining them in one example would make it harder to identify which language mode or option the code requires. A small experiment for each construct would give clearer evidence about diagnostics and toolchain compatibility.
Experiments need an exit-path inventory
My initial review would identify the resource owner and each point at which the function can stop using it. I would then compare that list with the scope containing the cleanup declaration. It is the evidence I would want before accepting a proposal that changes resource handling in C code.
The experimental status also affects the boundary of that work. I would keep the compiler version and the option explicit in the experiment, and retain the source cases used to assess it. The release gives developers an implementation to examine while the specification can still change. Feedback is more useful when it identifies a particular rejected jump or ambiguous ownership case and includes the smallest function that demonstrates it.