defer is a compile-time construct: the cleanup is copied into every exit path of the function. That is why a loop body and a branch are refused — a loop body's would fire once at function exit rather than once per iteration, and a branch would have to express "maybe registered", which a form copied into every exit path or into none cannot say. A let is neither. It is not a frame here: its bindings are function slots like any other and nothing is released at scope exit, so a let at the top level of a function body has exactly the function's extent and a defer written in it always registers. It was refused for a reason that does not apply to it. A let nested inside such a let has the same extent and the same permission; a let inside a while or an if has the loop's or the arm's, and inherits the refusal. The permission is granted again before every form of a body, never once around the body: check withdraws it as it starts, so granting it once would let the first defer through and refuse the second — and two resources acquired in one let is the case this exists for. defer-let.flan covers that one specifically, along with nesting, interleaved registration order across the let boundary, and an early return. The two refusals that stay now name what blocks them.
73 lines
2.5 KiB
Plaintext
73 lines
2.5 KiB
Plaintext
;;;; defer in a let whose extent is the function body.
|
|
;;;;
|
|
;;;; A let is not a frame here: its bindings are function slots like any other,
|
|
;;;; and nothing is released at scope exit (spec-memory.md, "When storage is
|
|
;;;; released"). So a let at the top level of a function body has exactly the
|
|
;;;; function's extent, a defer written in it always registers, and it is as
|
|
;;;; safe as one written at the top level. A let nested inside such a let has
|
|
;;;; the same extent and the same permission.
|
|
;;;;
|
|
;;;; The numbers differ per failure, so a wrong answer names its own cause.
|
|
|
|
(defvar order i64)
|
|
|
|
(defn note [n i32] (set order (+ (* order 10) (i64 n))))
|
|
|
|
;;; One defer in a let, with the acquisition it is paired with above it. This
|
|
;;; is the shape the relaxation exists for: acquire, defer the release beside
|
|
;;; it, then use it.
|
|
(defn one [] i64
|
|
(let [a 1]
|
|
(defer (note a))
|
|
(note 9))
|
|
order)
|
|
|
|
;;; Two resources in one let. This is the case a flag granted once per block
|
|
;;; instead of once per form gets wrong: the first defer registers and the
|
|
;;; second is refused.
|
|
(defn two []
|
|
(let [a 1 b 2]
|
|
(defer (note a))
|
|
(defer (note b))
|
|
(note 9)))
|
|
|
|
;;; A nested let still has the function's extent, so a defer in it registers
|
|
;;; too — and it registers *later* than the outer one, so it runs first.
|
|
(defn nested []
|
|
(let [a 1]
|
|
(defer (note a))
|
|
(let [b 2]
|
|
(defer (note b))
|
|
(note 9))))
|
|
|
|
;;; Registration order is one order across the boundary: a defer at the top
|
|
;;; level and a defer inside a let interleave by where they are written, not by
|
|
;;; which construct they are in. Written 1, 2, 3; run 3, 2, 1.
|
|
(defn mixed []
|
|
(defer (note 1))
|
|
(let [x 2]
|
|
(defer (note x))
|
|
(defer (note 3))
|
|
(note 9)))
|
|
|
|
;;; An early return runs them too, and innermost-first, exactly as it does for
|
|
;;; a defer at the top level. The let's binding is still live when the defer
|
|
;;; reads it, because the slot is the function's.
|
|
(defn early [] i64
|
|
(let [a 1]
|
|
(defer (note a))
|
|
(let [b 2]
|
|
(defer (note b))
|
|
(return 7)))
|
|
0)
|
|
|
|
(defn main [] i32
|
|
(set order 0) (print (one)) (println "") ; 9 — read before the defer runs
|
|
(set order 0) (one) (print order) (println "") ; 91
|
|
(set order 0) (two) (print order) (println "") ; 921 — reverse of writing
|
|
(set order 0) (nested)(print order) (println "") ; 921
|
|
(set order 0) (mixed) (print order) (println "") ; 9321
|
|
(set order 0) (print (early)) (println "") ; 7
|
|
(print order) (println "") ; 21
|
|
0)
|