flan/test/programs/vec.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

116 lines
4.3 KiB
Plaintext

;;;; (Vec T) — spec-memory.md, "The four container types" and "Allocators".
;;;;
;;;; ptr + len + cap + allocator, owning and move-only, over one type-erased
;;;; runtime. The element type appears nowhere in that runtime: size_of and
;;;; align_of are produced at the call site, which without generics is simply
;;;; the concrete call site. So this file being two element types with one
;;;; runtime behind them is the whole claim.
(defstruct Point [x i32 y i32])
;;; Ownership transfers on the call. The caller's binding is dead after this,
;;; which is what the refusal cases in test_acceptance assert.
(defn consume [v (Vec i32)] i32
(let [n (len v)]
(free v)
n))
;;; A Vec is returned by moving it out, so the callee's binding is the
;;; caller's. Nothing is released at function exit — there is no scope-end
;;; anything in this language.
(defn make [n i32] (Vec i32)
(let [v (vec-new i32)]
(dotimes [i n] (push v (* i i)))
v))
(defn sum [xs [i32]] i32
(let [total 0]
(dotimes [i (len xs)] (set total (+ total (at xs i))))
total))
(defn main [] i32
(let [v (vec-new i32)]
(println (len v)) ; 0
(push v 10)
(push v 20)
(push v 30)
(println (len v)) ; 3
(println (at v 0)) ; 10
(println (at v 2)) ; 30
;; A Vec element is a place, and the same bounds and epoch check stands
;; behind the value form and the place form.
(set (at v 1) 99)
(println (at v 1)) ; 99
;; slice over a Vec is a non-owning view: it copies ptr+len and never the
;; elements, and it carries no allocator, so nothing can be freed through
;; one. [at] and [len] over it are the array operations, unchanged, and
;; the three arities are the ones every other target has -- the tail form
;; included, which the Vec had no spelling for while it had a name of its
;; own. The view is of storage v owns: a push here would move it, and
;; nothing would say so.
(println (sum (slice v))) ; 139
(println (len (slice v 1 3))) ; 2
(println (at (slice v 1 3) 0)) ; 99
(println (len (slice v 1))) ; 2
(println (at (slice v 1) 1)) ; 30
;; clone is the only copy: assignment moves. The copy is independent, and
;; freeing it leaves the original alone.
(let [w (clone v)]
(set (at w 0) -1)
(println (at w 0)) ; -1
(println (at v 0)) ; 10
(free w))
;; reserve does not change the length, only the capacity, so a reserve
;; that succeeds is invisible except that the pushes after it do not grow.
(reserve v 64)
(println (len v)) ; 3
(push v 40)
(println (len v)) ; 4
;; The structural printer reaches both new types. Neither is followed: a
;; Vec's elements are printed through (slice v), which says at the call
;; site that it borrowed, and an allocator's contents are the runtime's and
;; its address is not stable across runs.
(println v) ; <vec>
(println context/allocator) ; <allocator>
(free v))
;; A second element type over the same runtime, and a struct element, so
;; that size_of and align_of are doing work rather than both being 4.
(let [ps (vec-new Point)]
(push ps (Point {.x 1 .y 2}))
(push ps (Point {.x 3 .y 4}))
(println (len ps)) ; 2
(println (.y (at ps 1))) ; 4
(free ps))
;; A Vec made against an explicit allocator records it, so free and clone
;; never need it named again. An arena cannot free one block, so this free
;; keeps the block — releasing it is free-all's job, and that is the
;; difference the capability set exists to state.
(let [a (arena-new 4096)]
(let [v (vec-new i32 a)]
(push v 7)
(println (at v 0)) ; 7
(free v))
(println (can-free? a)) ; false
(free-all a)
(arena-destroy a))
;; The pushes go into whatever the context names, with nothing passed.
(let [a (arena-new 4096)]
(with-allocator a
(let [v (vec-new i32)]
(push v 5)
(push v 6)
(println (+ (at v 0) (at v 1))) ; 11
(free v)))
(arena-destroy a))
(println (consume (make 5))) ; 5
0)