Vectors
This section documents Vec(T) — the language's owned, growable heap-allocated vector — per ADR-0066.
Type Form
Vec(T) is a built-in parameterized type constructor that lowers to TypeKind::Vec(VecTypeId) internally. The runtime representation is the 3-field aggregate { ptr: *T, len: i64, cap: i64 } (24 bytes on 64-bit targets, 8-byte aligned). Vec(T) is affine — it owns heap-allocated storage that the compiler-generated drop releases when the value goes out of scope.
Vec(T) is gated behind the vec preview feature; using the name in type position without --preview vec is rejected. When the element type T is linear, the resulting Vec(T) is itself linear (per ADR-0067): implicit drops are rejected and the user must drain the vector and call dispose to release the heap buffer.
Construction
Two associated functions construct a Vec(T):
Vec(T)::new() -> Vec(T)— empty vector,cap = 0,ptr = null.Vec(T)::with_capacity(n: usize) -> Vec(T)— empty vector withcap >= n. Allocates iffn > 0.
The variadic @vec(a, b, c, ...) intrinsic builds a Vec(T) from its arguments: cap = len = arg_count, each argument moved into a slot. At least one argument is required; element types unify to a single T. The element type MUST NOT be linear (linearity is propagated to the literal's elements which would be left un-consumed).
The @vec_repeat(v: T, n: usize) -> Vec(T) intrinsic builds a vector with n copies of v. v1 requires T: Copy; non-Copy T: Clone support is future work alongside the Clone synthesis path.
Methods
The instance method surface:
len(self: Ref(Self)) -> usize/capacity(self: Ref(Self)) -> usize/is_empty(self: Ref(Self)) -> bool— runtime field reads.push(self: MutRef(Self), value: T) -> ()— appendvalue, growing the buffer (doubling, min cap 4) onlen == cap.pop(self: MutRef(Self)) -> T— panic if empty, else remove and return the last element. (v1 returnsTdirectly; future work to wrap inOption(T).)clear(self: MutRef(Self)) -> ()— drop each live element ifTneeds drop, then setlen = 0. Capacity is preserved.reserve(self: MutRef(Self), additional: usize) -> ()— ensurecap >= len + additional.clone(self: Ref(Self)) -> Vec(T)— deep copy. v1: requiresT: Copy(memcpy path).
Indexing: v[i] reads bounds-checked at runtime, requires T: Copy. v[i] = x writes bounds-checked at runtime.
Slice borrowing
Range subscripts on a Vec(T) produce Slice(T) / MutSlice(T) borrows over the live [0..len] range, exactly as for fixed arrays. &v[..] / &mut v[a..b] use the runtime len field for bounds checking; the buffer pointer comes from the ptr field.
Iteration
for x in v over Vec(T) desugars to for x in &v[..]: a borrowed slice view that yields each element by value (for Copy T). The mutable form is deferred to a future ADR alongside the equivalent slice-mut iteration.
FFI / checked block
Inside a checked block:
v.ptr() -> Ptr(T)— read-only pointer to the buffer.v.ptr_mut() -> MutPtr(T)— mutable pointer to the buffer.v.terminated_ptr(s: T) -> Ptr(T)— ensurecap > len, writesatptr[len], return the buffer pointer. The sentinel byte sits outside the live[0..len]range and is overwritten by the nextpush.@parts_to_vec(p: MutPtr(T), len: usize, cap: usize) -> Vec(T)— take ownership of an existing buffer.
Drop
Dropping a Vec(T) runs the per-element drop loop (when T needs drop) over [0..len], then frees the buffer if cap > 0. The element drop dispatches via the same __gruel_drop_* machinery used for ordinary affine struct fields.
Dispose
v.dispose() is the explicit-release form (ADR-0067). It consumes self by-value, panics if len != 0 (with code 101 and message "panic: Vec::dispose called on a non-empty Vec"), then frees the heap buffer. After dispose the value is moved-out; no implicit drop runs at end-of-scope. For Vec(T) with non-linear T, dispose is an explicit alternative to implicit drop. For Vec(T:Linear) (see below), dispose is the only legal release path because implicit drops are rejected at compile time.
Linear elements
When the element type T is linear, Vec(T) is itself linear: the must-consume discipline propagates through the container. An implicit drop of Vec(T:Linear) is a compile error (the same diagnostic that governs ordinary linear values applies). Users must drain the vector (via pop or destructuring helpers) and then call dispose to release the buffer.
Vec(T:Linear)::clone is rejected because linear values do not conform to the Clone interface (cloning would create a second linear obligation). Vec(T:Linear)::clear and v[i] = x for Vec(T:Linear) are likewise rejected: both implicitly drop the displaced or cleared linear element. The prelude struct produced for Vec(T:Linear) omits these methods, so the dispatcher reports them as undefined. @vec(...) and @vec_repeat(...) likewise reject linear element types.
Byte-comparison and search methods
Vec(T) for T: Copy exposes element-wise comparison and subsequence search methods (ADR-0081):
eq(self: Ref(Self), other: Ref(Self)) -> bool—trueiff the two vectors have equal length and every element pair compares equal under primitive==. The==and!=operators on twoVec(T)values desugar to this method (via theEqinterface dispatch from ADR-0078).cmp(self: Ref(Self), other: Ref(Self)) -> Ordering— element-wise lexicographic comparison with length tiebreak (a shorter vector that is a prefix of a longer one comparesLess). The<,<=,>,>=operators desugar to this method (via theOrdinterface).contains(self: Ref(Self), needle: Slice(T)) -> bool—trueiffneedleoccurs as a contiguous subsequence withinself. Emptyneedlereturnstrue.starts_with(self: Ref(Self), prefix: Slice(T)) -> bool/ends_with(self: Ref(Self), suffix: Slice(T)) -> bool— leading / trailing subsequence tests. Empty argument returnstrue.concat(self: Ref(Self), other: Slice(T)) -> Vec(T)— allocates a freshVec(T)of lengthself.len + other.lencontainingselffollowed byother.selfis borrowed (not consumed).extend_from_slice(self: MutRef(Self), other: Slice(T)) -> ()— reserves additional capacity and appends every element ofotherin order onto the tail.
All six methods require T: Copy in v1; per-element interface dispatch for non-Copy T: Eq / T: Clone is future work tracked alongside the non-Copy clone deferral.
Vec method bodies live in prelude/vec.gruel as a pub fn Vec(comptime T: type) -> type returning an anonymous struct (ADR-0082). The compiler binds that declaration via @lang("vec") and routes method calls, indexing (v[i]), and static calls (Vec(T)::new() / Vec(T)::with_capacity(n)) to the prelude struct's instantiated methods. Per-element drop, the heap-buffer allocation policy (initial cap = 4, doubling growth), and bounds checks are all expressed in Gruel — adding a new method (Vec::last, Vec::find, etc.) is an edit to that one file.