-
-
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; | |
| } |
One critical feature that the
withstatement lacks, compared todefer, is handling initialization failure. While awith-elsecould be introduced to handle failures in the condition expression, the lack of a variable to store the evaluated value of the condition expression could pose a serious problem. This issue is shared withforloops, but it's more critical in this context because we are performing initialization rather than just checking a condition.
Possible solution:
mtx_t m = {};
with (int ret = mtx_init(&m, mtx_plain); ret == 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;
}
}
}
} else {
if (ret == thrd_error) {
// ...
}
}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.deferbeing low-level in this syntactic way is meant to mirror the existing practice (__attribute__((cleanup(...))),__try/__finallyin MSVC, etc.); a lot of these don't have this level of structure, and that means it can't be directly replaced by thiswithstatement.- The display of the
else-clause on thewithshows the error in having a (gated)withstatement:- merging two different domains (error handling and deferred action) means you now need to find a way to "cancel" the
withstatement or prevent entering it. - moving your mutex (or other important object) out of the
withso you can work with the error code means you've now negated one of the benefits ofwith(the reduced scope and lifetime of an object). This same problem happens with__try/__finallyin MSVC: https://thephd.dev/_vendor/future_cxx/papers/C%20-%20Improved%20__attribute__((cleanup))%20Through%20defer.html#intro-try.finally
- merging two different domains (error handling and deferred action) means you now need to find a way to "cancel" the
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.
I strongly agree that
deferis more foundational thanwith. But that’s precisely the point.deferhas too high degree of freedom. While restricting programmer freedom might go against the philosophy of the C language, it’s still important to find a sweet spot — just as we haveforandwhileloops despite the existence ofgoto. My concern is thatdefermight end up being confined to strictly limited use cases, much likegoto.Additionally,
deferintroduces a gap between initialization and cleanup. Carelessly inserted code might interfere with proper handling of thedeferstatement. In contrast,withhelps eliminate this gap.One critical feature that the
withstatement lacks, compared todefer, is handling initialization failure. While awith-elsecould be introduced to handle failures in the condition expression, the lack of a variable to store the evaluated value of the condition expression could pose a serious problem. This issue is shared withforloops, but it's more critical in this context because we are performing initialization rather than just checking a condition.The
withstatement isn't perfect either, but I believe it's important to constrain the excessive degree of freedom thatdeferintroduces.