# Milestone 19: `defer`, `errdefer`, and bare block statements. # # `defer ` runs the statement when the enclosing scope exits, in reverse # (LIFO) order, on every exit path. A bare `{ ... }` introduces a scope. Each # section returns a distinct code on failure; success falls through to 42. # The return value is captured before defers run, so the mutation here does not # change what is returned (Zig semantics). spill_check func() i32 { x i32 = 5 defer x = 999 return x } # A function-scope defer runs only at function exit; a `break` runs the loop-body # defer but NOT the enclosing function-scope defer. enclosing_defer_check func() i32 { v i32 = 0 defer v = v + 100 for 0..3 |i| { defer v = v + 1 if (i == 1) break } return v # 0->1 (i=0 fall-through), ->2 (i=1 break); the +100 runs after capture } CleanupError :: enum { bad } ExtraError :: enum { extra } CleanupErrors :: alias CleanupError | ExtraError explicit_cleanup func(fail bool, trace *mut i32) i32 ! CleanupError { defer trace^ = trace^ * 10 + 1 errdefer |err| { if (err == .bad) { trace^ = trace^ * 10 + 2 } else { trace^ = 99 } } defer trace^ = trace^ * 10 + 3 if (fail) return .bad return 7 } fail_cleanup func() i32 ! CleanupError { return .bad } try_cleanup func(trace *mut i32) i32 ! CleanupError { defer trace^ = trace^ * 10 + 4 errdefer trace^ = trace^ * 10 + 5 return try fail_cleanup() } widen_cleanup func(trace *mut i32) i32 ! CleanupErrors { errdefer |err| { match err { .bad: trace^ = trace^ * 10 + 6 .extra: trace^ = 99 } } return try fail_cleanup() } scoped_cleanup func(trace *mut i32) i32 ! CleanupError { errdefer trace^ = trace^ * 10 + 7 { errdefer trace^ = 99 } return .bad } multi_exit_cleanup func(direct bool, trace *mut i32) i32 ! CleanupError { errdefer |err| { if (err == .bad) trace^ = trace^ + 8 } if (direct) return .bad return try fail_cleanup() } main func() i32 { # 1. return value captured before defers run. if (spill_check() != 5) return 101 # 2. LIFO ordering, run at end of each loop iteration. r i32 = 0 for 0..1 |i| { defer r = r * 2 + 1 # registered first -> runs last defer r = r * 2 # registered second -> runs first _ = i } if (r != 1) return 102 # 0 -> (r*2)=0 -> (r*2+1)=1 ; FIFO would give 2 # 3. scoped bare block + scoped defer (defer fires at the closing brace, and # the block-local is not visible afterwards). a i32 = 1 { defer a = 4 c i32 = 3 _ = c } if (a != 4) return 103 # 4. `defer { ... }` block: all its statements run (in order) at scope close. s i32 = 0 { defer { s = s + 1 s = s * 10 } s = 5 } if (s != 60) return 104 # 5 -> 6 -> 60 # 5. `break` flushes the loop-body defer. bc i32 = 0 for 0..5 |i| { defer bc = bc + 1 if (i == 2) break } if (bc != 3) return 105 # i=0,1 fall-through + i=2 break # 6. `continue` flushes the loop-body defer. cc i32 = 0 for 0..3 |i| { defer cc = cc + 1 if (i == 1) continue cc = cc + 10 } if (cc != 23) return 106 # 10,+1 ; +1 (continue) ; +10,+1 # 7. break does not run an enclosing function-scope defer. if (enclosing_defer_check() != 2) return 107 # 8. errdefer is skipped on success; ordinary defers stay interleaved. trace i32 = 0 if ((explicit_cleanup(false, &trace) catch 0) != 7 or trace != 31) return 108 # 9. Explicit errors run errdefer and expose the captured error. trace = 0 if ((explicit_cleanup(true, &trace) catch 9) != 9 or trace != 321) return 109 # 10. Propagated errors run both errdefer and ordinary defer. trace = 0 if ((try_cleanup(&trace) catch 9) != 9 or trace != 54) return 110 # 11. Captures observe the widened enclosing error type. trace = 0 if ((widen_cleanup(&trace) catch 9) != 9 or trace != 6) return 111 # 12. An errdefer expires when its block exits normally. trace = 0 if ((scoped_cleanup(&trace) catch 9) != 9 or trace != 7) return 112 # 13. One captured errdefer can be replayed at several distinct error exits. trace = 0 _ = multi_exit_cleanup(true, &trace) catch 0 _ = multi_exit_cleanup(false, &trace) catch 0 if (trace != 16) return 113 return 42 }