Skip to content

Instantly share code, notes, and snippets.

@asd142513
Last active August 25, 2025 04:40
Show Gist options
  • Save asd142513/ee494971fc93b46a4ed81935e2e4261a to your computer and use it in GitHub Desktop.
Save asd142513/ee494971fc93b46a4ed81935e2e4261a to your computer and use it in GitHub Desktop.
/*
Original post:
https://thephd.dev/c2y-the-defer-technical-specification-its-time-go-go-go
Inspired by for loop and Python’s `with` statement:
https://en.cppreference.com/w/c/language/for.html
for ( init-clause ; cond-expression ; iteration-expression ) loop-statement
https://docs.python.org/3/reference/compound_stmts.html#with
Syntax:
with ( init-clause ; cond-expression ; cleanup-expression ) context-statement
Comparison: `thephd.dev defer` vs `with`
1. Automatically creates a new scope.
2. Supports only expressions — no deferred statements.
3. `break` is allowed within the `with` block.
4. Nested `with` statements are not allowed within init-clause, cond-expression, or cleanup-expression.
However, the context-statement can include `with`, just like in loop bodies.
5. No mid-scope defers.
- In the original `defer`, deferred statements could appear anywhere in the scope.
- This is suboptimal, as developers must read the entire block to find all deferred actions.
- The `with` statement improves clarity by forcing all deferred behavior to be declared at the start of the block.
6. Unlike `defer`, which helps maintain a flat block structure `with` introduces an additional level of nesting.
This is even worse in common `malloc`–`init_object` patterns as it introduces two levels of additional nesting.
Comparison: `for` loop vs `with` block
1. Trivially, context-statement is executed only once; `continue` is not allowed.
2. Unlike a `for` loop's iteration-expression, the cleanup-expression is evaluated when exiting via `break` or `return`.
Jump to outside of block with `goto` skips the cleanup-expression.
*/
#include <threads.h>
extern int do_sync_work(int id, mtx_t *m);
int main(void)
{
with (mtx_t m = {}; mtx_init(&m, mtx_plain) == thrd_success; mtx_destroy(&m)) {
for (int i = 0; i < 12; ++i) {
with (; mtx_lock(&m) == thrd_success; mtx_unlock(&m)) {
if (do_sync_work(i, &m) == 0) {
return 1;
}
}
}
}
return 0;
}
@ThePhD
Copy link

ThePhD commented Aug 25, 2025

I don't necessarily disagree that with and Python's stylings here are nicer to use (and restrict things to just expressions). But, there's a few flaws to this:

  • Statement Expressions can and always do immediately thwart any "we can just limit this to expressions" bit. It's an extension right now, but it's a fairly widely implemented one (even chibicc has it, I think the only compiler that can't handle it well that's in real use is Microsoft's).
  • with, like @fdwr stated, the combination of all of these things is just really a rearranging of the more fundamental operations. defer being low-level in this syntactic way is meant to mirror the existing practice (__attribute__((cleanup(...))), __try/__finally in MSVC, etc.); a lot of these don't have this level of structure, and that means it can't be directly replaced by this with statement.
  • The display of the else-clause on the with shows the error in having a (gated) with statement:

It's for that reason that I didn't pursue a Python-with or C#-using feature. I do think you can have this sort of feature to make working with deferred action easier, but I would prefer to start with the fundamental piece and have a general-purpose, no-strings-attached undo mechanism and then work up the ladder.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment