⚠️ This is a PROPOSAL DRAFT. Defer is semi-implemented
Development is not going to advance until LLVM backend is minimally stable.
This is still up to debate, we have covered goto, but we have not talked about longjmp, _Noreturn functions or signals.
The current idea (and this can be changed) is that the user may or may not want to run defers at some cancellation points, because maybe the user wants to fast-fail, among other reasons.
Also, some cancellation points are “easy” to catch, but some are impossible, like signals.
Nowadays, there are two ideas to leverage, that can coexists:
__builtin_unwind_defers().
auto defer_c = defer printf("C");, this registers the defer as a closure, then and call it when necessary defer_c();
Since both ideas are non-exclusive, we may implement assign defer statement, and make
__builtin_unwind_defers()to just write the closure calls in order.
#include <stdio.h>
_Noreturn void panic(const char *msg) {
fprintf(stderr, "%s\n", msg);
exit(1);
}
int main() {
defer printf("A\n");
if (fatal) {
defer auto printf("B\n");
__builtin_unwind_defers();
panic("fatal error");
}
return 0;
}
#include <stdio.h>
_Noreturn void panic(const char *msg) {
fprintf(stderr, "%s\n", msg);
exit(1);
}
int main() {
auto defer_a = defer printf("A\n");
if (fatal) {
auto defer_b = defer auto printf("B\n");
defer_b();
defer_a();
panic("fatal error");
}
return 0;
}
#include <stdio.h>
#include <stdbool.h>
/* Written lambdas instead of closures for simplicity */
__attribute__((fastcall))
static void defer_z() {
printf("Z\n");
}
__attribute__((fastcall))
static void defer_a() {
printf("A\n");
}
__attribute__((fastcall))
static void defer_b() {
printf("B\n");
}
__attribute__((fastcall))
static void defer_c() {
printf("C\n");
}
int main() {
char x; // stack garbage
// defer printf("Z\n");
while (true) {
lbl_break2_special: ;
// defer auto printf("A\n");
while (true) {
// type inference
// defer auto printf("B\n");
while (true) {
// defer auto printf("C\n");
if (x == 7) {
// -> run defer C
// -> run defer B
// -> run defer A
defer_c();
defer_b();
defer_a();
goto lbl_break3;
} else if (x > 100) {
// -> run defer C
// -> run defer B
defer_c();
defer_b();
// do not call defer A because lbl_break2
// is inside the scope of defer A
goto lbl_break2;
} else if (x == -1) {
// -> run defer C
// -> run defer B
// -> run defer A
defer_c();
defer_b();
defer_a();
// This one calls defer A,
// because it jumps before the defer registration;
// effectively, out of scope of defer A
goto lbl_break2_special;
} else if (!x) {
// funnel this scope-level
// -> run defer C
// -> run defer B
// -> run defer A
defer_c();
defer_b();
defer_a();
// funnel function-level
// -> run defer Z
defer_z();
exit(1);
} else {
// funnel this scope-level
// -> run defer C
// -> run defer B
// -> run defer A
defer_c();
defer_b();
defer_a();
return 1; // -> will run defer Z
}
// -> run defer C
{ printf("C\n"); }
}
if (x == 7) {
// -> run defer B
// -> run defer A
defer_b();
defer_a();
goto lbl_break3;
}
// -> run defer B
{ printf("B\n"); }
}
lbl_break2:
if (x == 7) {
// -> run defer A
defer_a();
goto lbl_break3;
}
// -> run defer A
{ printf("A\n"); }
}
lbl_break3:
return 0;
// -> run defers Z after any return label
}
Some possible optimizations to this approach are: