From 54b8a8918bb63c98a8f58e823033232243647562 Mon Sep 17 00:00:00 2001 From: zinc Date: Wed, 14 Oct 2026 10:39:47 +0000 Subject: zc: add the defer statement defer expr; and defer { ... } register code that runs when the enclosing block ends: at the end of the block, and at return, break and continue that leave it. the tokens are kept at the defer and compiled again at each exit (block_close, body, kw_return, kw_break, kw_continue), newest first, before the block's variables are released, so the analyzer sees a deferred rt.free(p) as the transfer of p where it runs. names mean what they meant at the defer; values are read when it runs. return, break and continue inside a deferred statement are errors, reported once. works in unchecked code and closures (own base per function context). --- docs/LEARN.md | 38 ++++++++++++++++++++++++++++++++++++++ docs/SPEC.md | 19 +++++++++++++++++++ 2 files changed, 57 insertions(+) (limited to 'docs') diff --git a/docs/LEARN.md b/docs/LEARN.md index a3588e7..d514d85 100644 --- a/docs/LEARN.md +++ b/docs/LEARN.md @@ -174,6 +174,44 @@ output: 0 5 6 ``` +### defer + +`defer expr;` or `defer { statements }` runs code when the enclosing block ends: after the last statement, and also on `return`, `break` and `continue` out of it. defers in a block run last registered first. the deferred code is compiled at each exit, so it sees the variables as they are when it runs, and `return e;` computes `e` before any defer runs. `defer rt.free(p);` hands the owner `p` over at that point: `p` stays usable until the block ends and is not freed twice. `return`, `break` and `continue` are not allowed inside a deferred statement. + +[example source](../examples/learn/34-defer.zc): + +```zc +namespace; +include rt; +fn say(n) { rt.put_n(n); rt.put_c(32); return 0; } +int work(int n) { + int* p = rt.new(int); + defer rt.free(p); + defer say(100); + *p = n * 2; + say(*p); + if (n > 2) return *p + 1; + say(1); + return 0; +} +fn main() { + for (let i = 0; i < 4; i++) { + defer { say(i); rt.put_c(10); } + if (i == 1) continue; + say(work(i)); + } +} +``` + +output: + +```text +0 1 100 0 0 +1 +4 1 100 0 2 +6 100 7 3 +``` + ## numbers and strings ### sized integers diff --git a/docs/SPEC.md b/docs/SPEC.md index 51ed5d1..f3e47a7 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -53,6 +53,7 @@ a name that looks like `x.y` where `x` is not a known module is the three tokens | `while` `for` `do` | S | | | loops (4.4 to 4.6) | | `switch` | S | | | 4.7 | | `break` `continue` `return` | S | | | 4.8 | +| `defer` | S | | | 4.14 | | `enum` | S | T | | 5.8 | | `include` | S | T | | 5.12 (a statement only by accident, 5.12.7) | | `embed` | S | T | | 5.11 | @@ -636,6 +637,24 @@ int x = asm { 2 3 + }; // 5 int y = 7; asm { y @ 1 + y ! } // y is 8 ``` +### 4.14 defer + +4.14.1 `defer expr;` and `defer { statements }` register a statement that runs when the block that contains the `defer` ends. the tokens are kept, not compiled, at the `defer`; the compiler compiles them again, in place, at every point where control leaves the block: the end of the block, and a `return`, `break` or `continue` that leaves it. (a function body is a block; so are a loop or `if` body, a `switch` case and a block expression.) the code is therefore duplicated at each exit. + +4.14.2 several defers in one block run in reverse order of registration. an early exit runs the defers of every block it leaves, innermost block first, and within a block newest first. `return` runs those of the whole function, `break` and `continue` only those inside the loop or switch they leave. + +4.14.3 the deferred code is evaluated when it runs, not when it is registered (as in the c defer proposal): it reads the current values of the variables it names. which variable a name means is fixed where the `defer` is written: only the locals declared before the `defer` are visible to it, so a variable that an inner block declares again, or one declared later in the block, is not seen (naming one that does not exist yet is error: y is used but never defined). + +4.14.4 `return e;` evaluates `e` first, then runs the defers, then returns the value; a defer that changes a variable `e` read does not change the result. + +4.14.5 a `defer` registers when control reaches it. a `defer` in an `if` branch belongs to that branch's block and ends with it; an early `return` before a `defer` in the same block does not run it. + +4.14.6 `return`, `break` and `continue` that would leave a deferred statement are errors: return can't be used in a deferred statement, break can't leave a deferred statement, continue can't leave a deferred statement. loops, `switch` and closures inside the deferred statement may use their own. a deferred statement is compiled once per exit, but an error in it is reported once. + +4.14.7 ownership (6): the analyzer sees the deferred statement at the exit, before the block's locals are released. `defer rt.free(p);` is the transfer of `p` at that point: `p` stays alive and usable from the `defer` until the block ends, it is not freed again nor reported as never freed, and if `p` was given away meanwhile (a second `rt.free(p)`, a call to a `.d` parameter, a store into a global) the deferred `rt.free(p)` is error: p: used after it was given away. + +4.14.8 defer works in `unchecked` code: the analyzer's cleanup is off there, but the deferred code runs. a defer keeps the checked or unchecked mode it was written in. a function that falls off its end runs its function-level defers before returning 0. + ## 5. declarations at the top level ### 5.1 items -- cgit v1.2.3