tur/vec
typed Vec[A]: parameterized growable array.
Parameterized growable array. Runtime layout identical to tur/vec.
Since: Phase TC1-A
Vec
(defstruct Vec :heap [A] (data :ptr<void>) (len :int) (cap :int))
parameterized growable array with element type A.
Since: Phase TC1
vec-new
(vec-new [A])
allocate a new empty Vec[A].
A new empty Vec[A] with len=0 and cap=0.
(vec-new) ; => empty Vec
Since: Phase TC1
vec-len
(vec-len [A])
return the number of elements in a Vec[A].
#reads v is the trusted claim that this body only READS the vector it is handed. It lets a refinement over (vec-len v) be treated as a function of frozen state inside a frozen region, so a bounded-index loop invariant can discharge. A mutator (vec-push!, vec-set-o!, ...) deliberately carries no such claim -- that asymmetry is how the frozen grant tells a reader from a writer, since the SIGNATURE cannot: both take the vector by value. See docs/guides/stateful-refinements-guide.md.
| v | Vec[A] pointer |
Element count.
(vec-len my-vec) ; => 3
Since: Phase TC1
vec-get
(vec-get [A])
return the element at index i (bounds-checked).
#reads v marks this a reader -- see the note on vec-len.
| v | Vec[A] pointer | |
| i | zero-based index |
The element at index i (typed A, stored as int64_t).
(vec-get my-vec 0) ; => first element
Since: Phase TC1
vec-push!
(vec-push! [A])
append an element to the Vec[A].
| v | Vec[A] pointer | |
| val | element of type A to append |
(vec-push! my-vec 42)
Since: Phase TC1
vec-pop!
(vec-pop! [A])
remove and return the last element of a Vec[A].
Ownership note for MULTI-WORD (> 8 byte by-value struct/ADT) elements: such an element rides its data slot as a heap box pointer. vec-pop! returns that box pointer on the carrier and shrinks len, so ownership of the box TRANSFERS OUT to the caller -- vec-free will no longer see it. The usual read pattern (:: (vec-pop! v) T) deref-copies the box to a value and then drops the box, which leaks it (there is no handle left to free). To REMOVE the last element without keeping it, use vec-drop-last! (which frees the box); to look at it, vec-get before popping. Single-word A (int/cstr/float/opaque/:heap ptr) has no box, so vec-pop! is leak-free there.
| v | Vec[A] pointer |
The last element of type A (as int64_t), or 0 if empty.
(vec-pop! my-vec) ; => last element
Since: Phase TC1
vec-drop-last-o!
(vec-drop-last-o! [A])
remove the last element and (when boxed) free its box.
The removal twin of vec-pop! that keeps NOTHING: it shrinks len and, when bit 0 of owned is set (a wide by-value element whose slot is an owned heap box), frees that box so it does not leak; when bit 1 is set (an rc<T> element) it releases the slot's strong reference. Unlike vec-pop! it returns no value, so there is no ownership-transfer hazard. Internal helper for the vec-drop-last! macro; not called directly.
| v | Vec[A] pointer | |
| owned | bit 0: the last slot is an owned element box; bit 1: the | |
| last slot holds a strong rc<T> reference the Vec owns |
Since: multiword-element-box-lifecycle
vec-drop-last!
(vec-drop-last! [v])
remove the last element of a Vec[A], freeing its box.
A macro so the multi-word-element decision folds at emit time: it binds v once and forwards it to vec-drop-last-o! along with (tur-vec-elem-wide? v). For a wide by-value element the removed slot's heap box is freed; for a single-word element only len shrinks. Use this instead of a discarded (vec-pop! v) when you want to drop the last element without keeping it -- vec-pop! would transfer the box out and leak it (see vec-pop!).
| v | Vec[A] pointer |
(vec-drop-last! my-vec)
Since: multiword-element-box-lifecycle
vec-set-o!
(vec-set-o! [A])
overwrite the element at index i, freeing the old box when boxed.
Internal helper for the vec-set! macro. A multi-word by-value element rides its data slot as an owned heap box; overwriting the slot orphans the old box, so when bit 0 of owned is set the old data[i] box is freed before the new value is stored. When bit 1 is set (an rc<T> element) the old slot's strong reference is released instead. When owned is 0 (single-word, non-rc A) the slot is an inline value / shared pointer the Vec does not own, so it is overwritten directly.
Aliasing caveat: freeing the old box invalidates any raw carrier a caller captured from an earlier (vec-get v i). The normal read pattern (:: (vec-get v i) T) deref-copies immediately, so no live alias survives the overwrite -- this holds under the collection's value-semantics ownership model (the Vec owns its element boxes).
| v | Vec[A] pointer | |
| i | zero-based index | |
| val | new element value of type A | |
| owned | bit 0: the slot is an owned element box; bit 1: the slot holds | |
| a strong rc<T> reference the Vec owns |
Since: multiword-element-box-lifecycle
vec-set!
(vec-set! [v i val])
overwrite the element at index i (freeing any old element box).
A macro so the multi-word-element decision folds at emit time: it binds v/i/ val once (preserving left-to-right evaluation) and forwards them to vec-set-o! along with (tur-vec-elem-wide? v). For a wide by-value element the overwritten slot's old heap box is freed; for a single-word element the slot is overwritten directly. See vec-set-o! for the aliasing caveat.
| v | Vec[A] pointer | |
| i | zero-based index | |
| val | new element value of type A |
(vec-set! my-vec 0 99)
Since: Phase TC1
tur-vec-elem-wide?
(tur-vec-elem-wide? [A])
1 if the Vec's element type A is a multi-word (> 8 byte) by-value struct/ADT that rides its data slot as an int64 heap-box pointer, else 0. Compiler-intercepted (src/compiler/emit_expr.c) as an EMIT-TIME type query that peels A out of the (Vec A) spine and reuses the same type_is_wide_byval_adt predicate the element-boxing bridge keys on; this pure-Turmeric fallback body returns 0 for the interpreter (where an element rides as a TuriStruct pointer, never a C box) and for any call the backend does not intercept. vec-free threads it to vec-free-o so a Vec[Point]-style boxed buffer releases its element boxes on free.
Since: multiword-element-box-lifecycle
tur-vec-elem-rc?
(tur-vec-elem-rc? [A])
1 if the Vec's element type A is an rc<T>, whose data slots hold a strong reference the Vec owns, else 0. Compiler-intercepted (src/compiler/emit_expr.c) as an EMIT-TIME type query, exactly like its sibling tur-vec-elem-wide?; this pure-Turmeric fallback body returns 0 for the interpreter (where an rc rides as a first-class value, never a carrier-erased control-block pointer) and for any call the backend does not intercept.
vec-free / vec-set! / vec-drop-last! thread it as BIT 1 of the owned flag (bit 0 being tur-vec-elem-wide?), so a Vec[rc<T>] releases each element's strong reference at the same points a Vec[Point] frees each element box. The matching increment is stamped at the push site by elab (own_carry_for_arg in src/compiler/elab_call.c).
Since: collections-cannot-hold-rc-values
vec-free-o
(vec-free-o [A])
free a Vec[A], its data buffer, and (when boxed) each per-element heap box.
A multi-word by-value element A cannot ride the int64 data slot inline, so the concrete->carrier escaping bridge boxes it (malloc + copy) at the push site and stores the box pointer in data[i]. Vec is mutable and NOT structurally shared, so the Vec owns those boxes outright: when bit 0 of owned is set (threaded by vec-free via the emit-time tur-vec-elem-wide? query) each data[i] box is freed before the data buffer.
Bit 1 (threaded from tur-vec-elem-rc?) is the refcount twin: for a Vec[rc<T>] each slot holds a strong reference taken at the push site, so every one is released before the buffer goes. When owned is 0 (any single-word, non-rc A -- :int, :cstr, :float, an opaque handle, a :heap pointer) the slot holds an inline value / shared pointer that the Vec does not own, so only the data buffer and header are freed.
| v | Vec[A] pointer to free | |
| owned | bit 0: each data slot is an owned element box; bit 1: each | |
| data slot holds a strong rc<T> reference the Vec owns |
Since: multiword-element-box-lifecycle
vec-free
(vec-free [v])
free a Vec[A], its data buffer, and any per-element boxes.
A macro (not a plain defn) so the multi-word-element decision folds at emit time: it binds v once and forwards it to vec-free-o along with (tur-vec-elem-wide? v), which the compiler resolves per monomorphization to 1 for a wide by-value element type (whose data slots are owned heap boxes) and 0 otherwise. The interpreter expands the same form; there tur-vec-elem-wide? is 0 and vec-free-o routes to native_vec_free_o (the per-element boxes do not exist in the tree-walker).
| v | Vec[A] pointer to free |
(vec-free my-vec)
Since: Phase TC1
vec-push-each__
(vec-push-each__ [v & xs])
internal helper for the vec-of macro.
Expands a sequence of pushes onto v followed by v itself as the final value. Recursive: each step emits (do (vec-push! v x) ...).
tur-vec-homog__
(tur-vec-homog__ [A])
internal: compile-time element homogeneity check.
Both parameters share the single type variable A, so a call with two differently-typed arguments is a TUR-E0001 at elaboration. The body is a no-op; the call exists only to make the type-checker enforce that adjacent vec-of elements have the same type. No allocation (fixed arity).
vec-homog-chain__
(vec-homog-chain__ [& xs])
internal: pairwise-chain tur-vec-homog__ over xs.
Emits (tur-vec-homog__ x0 x1), (tur-vec-homog__ x1 x2), ... so all elements transitively unify to one type. Zero or one element: no check.
vec-empty-like__
(vec-empty-like__ [A])
internal: a fresh empty Vec[A] whose element type A is grounded by a type-witness value.
(vec-new) on its own returns a bare :A result that summarizes to the int64 carrier, so a (let [v (vec-new)] ...) binding freezes at the carrier and later element reads (show/eq? dispatch) cannot recover the concrete element type -- a Vec[cstr] then shows/compares by pointer. By threading one already-typed element through witness : A, the returned (:: (vec-new) (Vec A)) is a genuine (Vec A) whose head carries the concrete element type, so the vec-of result dispatches element typeclass methods correctly. The witness value itself is unused (only its type is consumed), so it never widens or truncates the stored data.
vec-of
(vec-of [& xs])
variadic Vec[A] literal.
| & xs -- zero or more elements, all of type A. |
A fresh Vec[A] populated with xs in order. The element type A may be
any single-word type (:int, :cstr, :bool, :nil, :float, an opaque
handle, or a heap struct/ADT pointer); elements are stored through an
:int carrier slot, so a :float element round-trips as a bit-reinterpret.
The result's static type is a genuine (Vec A) -- its head carries the
concrete element type -- so (show (vec-of "a" "b")) renders "[a b]" and
(eq? (vec-of "a") (vec-of "b")) compares by the element's Eq instance
rather than by carrier identity. The handle composes with vec-len,
vec-get, vec-push!, etc.; vec-get returns :A, so a non-int element is
read with an ascription, e.g. (:: (vec-get v 0) :float).
(vec-of) ; => empty Vec (vec-of 1 2 3) ; => Vec containing 1, 2, 3 [1 2 3] ; => same, with -Xdata-literals
Note: all elements must unify to a single type. Mixing types -- e.g.
(vec-of 1 "x" 3.14) -- is a type error (TUR-E0001 on the offending element). For heterogeneous fixed-arity collections, use tupleN. Aggregate (multi-word struct/ADT) element types are not supported by the carrier slot; those need a per-element typed layout (see TS3b).
Note: each element expression is evaluated exactly once (the first is
bound once and reused as both the type witness and the first push).
Note: under -Xdata-literals, a [...] vector in expression position lowers
to this macro, so [1 2 3] is sugar for (vec-of 1 2 3). See docs/guides/data-literals-guide.md.
Since: Phase 2 cons-in-docs cleanup
vec-eq?
(vec-eq? [A])
compare two Vec[A] element-wise using a comparator.
This helper hands the comparator RAW slot words -- it reads a->data[i] straight out of the buffer rather than passing a value across the carrier boundary. So do list-eq?, option-eq?, result-eq? and pair-eq?, each from its own payload slots; map-eq? and set-eq-cmp? are the exception, handing over a HAMT-stored carrier box instead. The difference matters only for an element whose word is not a box, a niche (Option P) above all: it rides its slot as the payload pointer itself.
Either spelling works, here and at every one of those helpers: declare the comparator's parameter types, or write the comparator inline at the call, and the value is bridged for you whichever convention it arrived in. A NAMED comparator with erased parameters over such an element cannot be given the convention and is refused with TUR-E0715.
| v1 | first Vec[A] | |
| v2 | second Vec[A] | |
| cmp-fn | comparator fn [a :A b :A] :bool, receiving raw slot words |
true if both vecs have the same length and all element pairs satisfy cmp-fn.
(vec-eq? va vb (fn [a b] (= a b))) ; => true
(vec-eq? vo wo (fn [a : (Option String) b : (Option String)] : bool
(eq? a b))) ; => true
Since: Phase TC1
vec-data-get__
(vec-data-get__ [data :ptr<void> i :int] :int)
element-wise equality using the element type's Eq instance.
vec-data-get-checked__
(vec-data-get-checked__ [data :ptr<void> i :int len :int] :int)
bounds-checked indexed read over a raw data ptr.
vec-get-byval
(vec-get-byval [A])
by-value Vec[A] indexed read (Option C twin of vec-get).
vec-eq-loop
(vec-eq-loop [^Eq A])
pure-Turmeric element-wise Vec[A] comparison (TCO'd index loop).
Path A specializes this per element type; the self-tail-call lowers to a goto loop inside the spec (TCO-in-ABI-specs, emit_fns.c eligibility gate), so deep iteration on large vecs does not grow the C stack. Unlike the inline-C vec-eq? carrier helper, the receiver flows by value: vec-get is redirected to vec-get-byval inside the spec body, so no (:: x :int) relabel and no concrete->carrier bridge is minted at the Eq [Vec] dispatch site.
The ^Eq A constraint scopes the element Eq dictionary and each pair is compared with (eq? (:: (vec-get x i) A) (:: (vec-get y i) A)) directly -- mirroring vec-show-loop's element dispatch. This is deliberately NOT a comparator-closure design: an (fn [a b] (eq? a b)) lambda passed in from the Eq[Vec] instance defaults its params to the int64 carrier and bakes a single non-specialized Eq[int] closure (pointer/word comparison), which is a silent miscompare for cstr and every other content type. Reading and ascribing the element to A here re-drives dispatch per ABI specialization, so Eq[cstr] (strcmp), Eq[bool], etc. are threaded correctly.
| x | first Vec[A] | |
| y | second Vec[A] | |
| i | current index (start at 0) | |
| len | shared length (both vecs are known equal-length by the caller) |
true if x[i..len) equals y[i..len) under Eq[A].
(vec-eq-loop a b 0 (vec-len a)) ; => true
Since: Phase M4c (TCO-in-ABI-specs)
Eq[Vec]
(definstance Eq [Vec])
element-wise equality using the element type's Eq instance.