flan/test/programs/handles.flan
Joseph Ferano e933f5a84c The projectile asks, and is told the thing it was chasing is gone
The surface: (pool-new T), (insert p x) answering a handle, (resolve p h)
answering (Option (Ptr T)), (release p h) answering whether this call was
the one that released it, (len p) and (live p), and (pool-handle p i) for
enumeration. free extends to the pool and refuses a handle by name, because
a handle owns nothing and consuming one copy would say nothing about the
others.

resolve answers a pointer rather than a value because spec-memory.md's own
worked example does, and says why a line above it: a pattern binding binds
a value, and a copy cannot be written back.

test/programs/handles.flan prints <handle 1:1> and <handle 1:3> for the same
slot before and after a death, and the projectile still holding the first
gets -1 rather than the newcomer's 99.
2026-09-13 07:55:24 +07:00

102 lines
4.6 KiB
Plaintext

;;;; (Handle T) and (Pool T), spec-memory.md — "Cross-referencing long-lived
;;;; objects uses (Handle a) into a pool, never a raw pointer or slice. A
;;;; stale handle is detectable."
;;;;
;;;; The thesis, in one program: something holds a reference to an entity; the
;;;; entity dies; the slot is reused by a different entity; and the old
;;;; reference answers "gone" instead of answering wrong. Every other case
;;;; here is secondary to that one.
;;;;
;;;; It is all one function because a Pool is move-only exactly as a Vec is,
;;;; so passing one to a helper *consumes* it — there is no borrowing
;;;; parameter in the language yet. That is not a pool question and this
;;;; program does not work around it; see BUILT.md.
(defstruct Enemy [hp i32 kind i32])
;; The projectile does not hold an Enemy and does not hold an index. It holds
;; a handle, which is a number that owns nothing and copies freely — which is
;; why a struct may contain one where it may not contain a Vec.
(defstruct Projectile [target (Handle Enemy) damage i32])
(defn main [] i32
(let [pool (pool-new Enemy)]
(let [a (insert pool (Enemy {.hp 10 .kind 1}))
b (insert pool (Enemy {.hp 20 .kind 2}))
c (insert pool (Enemy {.hp 30 .kind 3}))
sum 0]
(println (len pool)) ; 3 slots handed out
(println (live pool)) ; 3 of them live
;; Enumeration, which is what a world arena and an owned region do not
;; give and which migrate-instances will need. (len p) is the slot
;; high-water, so 0..(len p) visits every slot ever handed out, and
;; (pool-handle p i) says which of them are still live.
(dotimes [i (len pool)]
(match (pool-handle pool i)
(Some h) (match (resolve pool h)
;; resolve yields a *pointer*, not a copy: mutating the
;; pooled thing in place is what a pool is for, and a
;; pattern binding binds a value.
(Some e) (set sum (+ sum (.hp e)))
None (do))
None (do)))
(println sum) ; 60
;; A write through a resolved pointer is a write to the pooled entity.
(match (resolve pool b)
(Some e) (set (.hp e) 21)
None (do))
(match (resolve pool b)
(Some e) (println (.hp e)) ; 21
None (println -1))
;; ── The thesis ────────────────────────────────────────────────
;; A projectile chasing b. b dies. The slot is reused by a fourth
;; enemy, which lands in exactly that slot — and the projectile's
;; handle says so rather than chasing the newcomer.
(let [shot (Projectile {.target b .damage 5})]
(println (release pool b)) ; true — this call released it
(println (release pool b)) ; false — it was already gone
(println (live pool)) ; 2
(let [d (insert pool (Enemy {.hp 99 .kind 4}))]
;; Printed as index:generation. Same slot, later generation — the
;; two halves of the answer, visible.
(println b)
(println d)
(println (= d b)) ; false
(println (= d d)) ; true
(match (resolve pool (.target shot))
(Some e) (println (.hp e))
None (println -1)) ; -1, not 99
(match (resolve pool d)
(Some e) (println (.hp e)) ; 99
None (println -1))
(println (len pool)) ; still 3 slots
(println (live pool)) ; 3 live
;; A zeroed handle is generation 0, which is even, and a live slot's
;; generation is always odd — so ZII gives a handle field the right
;; meaning for free rather than pointing it at slot 0.
(let [z (Projectile {.damage 1})]
(println (.target z))
(match (resolve pool (.target z))
(Some e) (println (.hp e))
None (println -1))) ; -1
;; a and c are untouched by any of it.
(match (resolve pool a)
(Some e) (println (.hp e)) ; 10
None (println -1))
(match (resolve pool c)
(Some e) (println (.hp e)) ; 30
None (println -1))
;; spec-memory.md's first release point, applied to the owner. Every
;; handle into it is stale afterwards and says so — which is a
;; better afterlife than a freed Vec's binding gets, that one being
;; a compile error the checker can see and this one an answer it
;; cannot.
(free pool)
0)))))