flan/test/programs/vec-global.flan
Joseph Ferano 9ce51ba94e One slice over everything with elements, and the warning at the push
as-slice was a warning, not an operation. The input type already decides
which of the two things happens — a Vec can only be borrowed, an array or a
string can only be viewed, and no call site picks between them — so the second
name expressed no choice a reader could make. And it warned at the moment the
view is taken, which is the one moment nothing is wrong; the danger arrives
later, at the push. slice now takes a Vec at all three arities and as-slice
is gone.

(slice v lo) was free, and is the arity the Vec never had: the runtime already
reads a hi of -1 as "to the end", so the tail form passes the caller's lo and
the same -1 — no slot, no length read, no second evaluation. The merge is
entirely in the checker; the Vec path builds the flan_vec_as_slice call it
always built and neither backend has a line about any of it.

A Vec a call returned is refused at every arity, and not for the array's
reason. (slice (mk)) over an array dangles. (slice (make-vec)) does not — the
storage outlives the expression — but the header is a temporary, so nothing
can ever free the block. The refusal says that and names the let.

The name's own refusal sits in ordinary_call after every table, so a program
that defines an as-slice still reaches its own. It reads for somebody who has
never heard of the old name and writes the call back out, spelling each
argument that is a name or a number.

The warning moved to where it bites: BUILT.md gains a section beside the Vec
table and the push row points at it, spec-memory.md's Borrowing says the same.
Investigated and deliberately not built — a diagnostic for a live view at the
push. (reserve v 100) then a slice, a push and a read is correct code under
the contract the spec chose, so any flag on it is a false positive by the
language's own semantics rather than by an approximation. FIX.org has the
finding and the syntactic sketch that does not work.
2026-09-21 09:51:35 +07:00

59 lines
2.2 KiB
Plaintext

;;;; A global of move-only type, which is allowed, and the one rule that makes
;;;; it allowed: reading a global Vec is always a borrow and never a move. The
;;;; lifetime question ownership tracking exists to answer has a constant
;;;; answer here — the process's — so nothing may take the global and nothing
;;;; may free it, and with no owner to hand over there is no double free to
;;;; catch. The refusals that enforce that are in test_flan.ml.
;;;;
;;;; A zeroed Vec is a real empty Vec, so the global starts as one and is
;;;; loaded by whoever loads it. What this program pins is that the loading
;;;; happens once and survives: [entry] is called twice, the way a re-entered
;;;; main would be, and the second call finds the data the first one left.
(defonce the-data (Vec u8))
(defonce counts (Map u8 i64))
;; Reading it here is a borrow. So is reading it in [total] below, which is the
;; case the old rule could not express: two functions holding the same global
;; at once is fine exactly because neither of them can free it.
(defn loaded? [] bool (> (len the-data) 0))
(defn load [] ()
(when (not (loaded?))
(set the-data (vec-new u8))
(dotimes [i 5] (push the-data (u8 (* i 3))))))
(defn total [] i64
(let [s (i64 0)]
(dotimes [i (len the-data)] (set s (+ s (i64 (at the-data i)))))
s))
;; Mutating in place, through the global rather than through a copy of it. The
;; aliasing contract is the one every Vec has (spec-memory.md, "Borrowing"): a
;; push may reallocate and invalidate a slice taken before it, and that is the
;; programmer's, here as much as for a local.
(defn bump [] ()
(set (at the-data 0) (+ (at the-data 0) 1))
(push the-data 100))
(defn entry [] ()
(load)
(bump)
(put counts 1 (total))
(println (len the-data))
(println (total))
(match (get counts 1) (Some v) (println v) None (println "?")))
(defn main [] i32
(entry)
;; The second run. Nothing re-initialises the global between them, so the
;; length keeps climbing and the loaded data is the same block it was.
(entry)
(println (len (slice the-data)))
;; A copy is the one thing something else may own, and freeing that copy
;; leaves the global untouched.
(let [c (clone the-data)]
(println (len c))
(free c))
(println (len the-data))
0)