The original arena deliberation (arena-allocation-semantics.md)
fixed promotion walkers (D1), escape rules (D2), handler plumbing (D3), and
nesting (D4). Six semantic questions about the expression-form behavior of
with arena { expr } were deferred and now block heap-promotion codegen
wiring (task dvpq88):
- Tail-return semantics of
with arena { expr }. - Early
return/?insidewith arena { }. - Handler composition order for
with h1, arena, h2 { body }. - How the outer promotion target is threaded into nested arenas.
- Whether
with arena { }discharges! Arenaon the enclosing function. - RAII ordering for
with arena, file = open(...) as file { body }.
This deliberation resolves all six so implementation can proceed against one consistent spec.
Five panelists (systems, web/scripting, PLT, DevOps/tooling, AI/ML) voted independently on each question.
Q1. Tail-return semantics.
Result: A — trailing expression is the block's value, promoted at } (5-0).
Options considered:
- A:
with arena { body }evaluates tobody's tail expression, deep-copied viablink_promote_<T>at the closing}. Tail position propagates throughif/matcharms. - B: Statement-only; values extracted via a pre-block
outvarbinding. - C: Explicit
return exprrequired inside the block.
Votes:
- Systems: A. The walker runs exactly once, at a syntactically obvious point. B needs an out-parameter calling convention just for arenas — a special case that poisons every codegen decision downstream. C forces early-return machinery onto the common path.
- Web: A.
let x = with arena { f() }is the shortest form a user can write and matches Rust/Kotlin/OCaml priors. B is a teaching disaster — "arena blocks don't return values like other blocks" is a foot-gun users will hit within five minutes. - PLT: A. Block-expression semantics preserve subject reduction: the
expression
with arena { e }has the same type ase. B breaks the uniformity of the expression language; C conflates control-flow with result-extraction. - DevOps: A. One promotion point per block = one span for diagnostics. B scatters promotions across assignment sites.
- AI/ML: A. Aligns with the strongest training signal (Rust/Kotlin block expressions). B/C introduce novelty with no matching corpus.
Q2. Early return and ?.
Result: A — promote into nearest enclosing arena (else caller target else GC), then exit(false), then propagate (5-0).
Options considered:
- A:
return expr/expr?insidewith arena { }promotesexprinto the same target the tail expression would resolve to at that program point, runsarena.exit(false), then propagates. - B:
return/?skip promotion; callers of an! Arenafunction that returns early get a dangling pointer. (Unsound.) - C:
return/?forbidden insidewith arena { }.
Votes:
- Systems: A. The tail-position predicate (Q1) must be the single source
of truth reused by the tail-expression path,
return, and?. Two implementations = subject reduction bugs. B is unsound. C cripples the feature. - Web: A. Users already reach for
?more than any other control-flow construct. Forbidding it inside arenas would be an onboarding cliff. - PLT: A. Subject reduction demands that early and tail exits have the same semantics with respect to region closure. B violates type safety.
- DevOps: A. Error-flow path matches happy-path — diagnostics stay consistent.
- AI/ML: A. Models expect
?to propagate everywhere; carving out an exception would produce mysteriously-broken generations.
Q3. Handler composition order.
Result: A — L-to-R enter, R-to-L exit; promotion inside arena.exit() against the snapshot (5-0).
Options considered:
- A: Standard LIFO
BlockHandlerordering. Promotion happens insidearena.exit()against the TLS snapshot captured atarena.enter(). Handlers right ofarenarunexitbefore promotion (see pre-promotion state); handlers left runexitafter (see post). - B: Promote before any handler's
exit, regardless of position. - C: Promote after all handlers'
exit.
Votes:
- Systems: A. Matches existing
BlockHandlercontract (§4.6.3). Any other ordering requires a second handler protocol just for arenas. - Web: A. Positional composition is the only rule a user can predict
without reading the compiler — "position in
withclause = position in teardown timeline." - PLT: A. Preserves the algebraic identity for handler composition:
with h1, h2 { e }andwith h1 { with h2 { e } }are observationally equivalent. B and C break this for arena. - DevOps: A. Traces render as a clean LIFO timeline. Requires a
canonical timeline example in
blink llms --topic arena— users will not derive this correctly from prose alone. - AI/ML: A. One rule (LIFO) generalizes across all handler kinds.
Q4. Outer target threading.
Result: A — TLS snapshot captured at arena.enter() into the BlockHandler restore struct (5-0).
Options considered:
- A:
arena.enter()reads__blink_current_arenaonce, stores it in its own restore struct, and passes it toblink_promote_<T>at exit. - B: Separate TLS slot for "outer promotion target."
- C: Walk the BlockHandler stack at promotion time.
Votes:
- Systems: A. One TLS read at
enter, one struct field atexit. B doubles TLS pressure for no benefit. C is an O(depth) stack walk on every}. - Web: A. Already matches how other BlockHandlers capture prior state (transactions, locks). No new mechanism.
- PLT: A. The snapshot is the natural representation of "outer region at this point." B introduces a parallel state that can desynchronize.
- DevOps: A. Debug output reads
handler.snapshot_targetas one field — trivially printable. - AI/ML: A. Uniform with other handler patterns in the codebase.
Q5. ! Arena escape boundary.
Result: A (outcome) with PLT's C framing — with arena { } is the escape boundary (4-1, PLT dissent on framing, adopted).
Options considered:
- A:
with arena { }"discharges"! Arena: an! Arenacallee inside the block does not force! Arenaon the enclosing function. - B:
! Arenaalways propagates;with arena { }does not affect signatures. - C:
with arena { }is the escape boundary for the! Arenamarker effect — escape analysis stops at the block, not a discharge mechanism.
Votes:
- Systems: A. Observationally correct: the arena block bounds the region where arena allocations are observable.
- Web: A. Signature hygiene — users don't want
! Arenato climb transitively through their call graph. - PLT: (dissent) C. D3 already locked in "
! Arenais a marker effect consumed by escape analysis, not an algebraic effect with evidence-passing dispatch." Using "discharge" vocabulary risks future contributors wiring handler-evidence plumbing that doesn't exist. Observationally identical to A; the framing matters. - DevOps: A. Diagnostic surface — "remove
! Arenabecause it's contained in awith arenablock" is writable as an error message. - AI/ML: A. Same behavior users expect from borrow-checker-style region containment.
Spec adopts PLT's C framing with A's outcome: the spec calls the block an "escape boundary" rather than a "discharge point." Behavior is identical; wording preserves the D3 invariant.
Q6. RAII resources inside arena block.
Result: A — body → promote(tail) → LIFO cleanup of non-arena resources → arena.exit() (5-0).
Options considered:
- A:
bodyruns with all resources live, tail is promoted while resources still live, then LIFO cleanup of non-arena Closeables, thenarena.exit(). - B: Close all Closeables before promotion.
- C: Destroy arena before closing Closeables.
Votes:
- Systems: A. Promotion may read through resource-backed buffers (e.g., a value computed from a file's mapped region). B prematurely closes the source of truth. C leaves dangling arena pointers in Closeable destructors.
- Web: A. Matches the mental model "resources live for the block, including while the block is producing its value."
- PLT: A. Tail expression must be evaluated while all bindings in scope are live. But tail values that retain references into a Closeable's buffers must be rejected (E0700) — the promoted value is about to outlive the resource.
- DevOps: A. Diagnostic: E0700 must span both the capturing expression
and the
as filebinding. Without that, the error is unfindable. - AI/ML: A. Parallels Python
withsemantics; no novel rule to learn.
| Criterion | Pass/Fail | Note |
|---|---|---|
| Learnability | ✅ Pass | A-across-board aligns with Rust/Kotlin/Python priors; one worked example covers the composition subtlety. |
| Consistency | ✅ Pass | Reuses BlockHandler, with…as, leftmost-wins composition — no new syntax. |
| Generability | ✅ Pass | let x = with arena { f() } is the shortest common form. |
| Debuggability | "Promote inside arena.exit()" is an invisible step at }; needs --blink-trace codegen to emit a spanned event. Tooling concern, not spec-level. |
|
| Token Efficiency | ✅ Pass | No ceremony added. |
Fails: 0-1. Proceed. Debuggability is an implementation constraint on tracing infrastructure, not a spec redesign.
- Single tail-position predicate (Q1, Q2, Q6): implement ONE function consumed by escape analysis and codegen. Two implementations = subject reduction bugs.
- Marker-effect hygiene (Q5, D3):
! ArenaMUST NOT participate in evidence-passing codegen. Assert in--debugbuilds. - Trace event for promotion (debuggability): emit a spanned event
with stable name
arena.promotein--blink-trace codegenand--trace allso users can locate invisible promotion points. - Diagnostic content (web, devops): E0700 must print the resolved
promotion target (
promoted into: outer arena at <span>orpromoted into: GC heap). Without this, nested-arena debugging is blind. - Tail captures Closeable (Q6 PLT): classify as E0700 with a span
pointing at both the capturing expression and the
as filebinding. - Canonical timeline example (Q3 PLT):
blink llms --topic arenamust include the step-by-step execution order forwith h1, arena, h2 { body }. - No
redundant ! Arenaauto-fix yet (devops Q5): lint warning when! Arenais on a function whose! Arenasites are all inside awith arena { }— candidate follow-up task. - Coroutine / resumable continuation precondition: if Blink ever adds
resumable continuations that can suspend inside
with arena, the TLS snapshot becomes stale. Documented as a future precondition.