Lowent Manual←↑→

vecgen — generic self-growing vector vec t a

Source
lib/vecgen.low
Layer
L1 — storage (carries an allocator)
Capabilities
none — open is handed an allocator

An array holding any type that grows itself when short of room. It has two axes — element type t × allocator type a. One source yields instances like vec#u32#allocs.bump_bytes and vec#u32#allocs.bump_aligned (visible in --ir), with no vtable and no indirect calls — the policy sits outside the container, and choosing costs nothing (chapter 22, chapter 34).

let g option (vecgen.vec u32 allocs.bump_bytes) . using bump be vecgen.open u32 2 .
guard is_some g . else return 1 .
var v vecgen.vec u32 allocs.bump_bytes . be some_value g .
guard vecgen.append u32 allocs.bump_bytes v 100 . else return 2 .
guard vecgen.append u32 allocs.bump_bytes v 101 . else return 3 .
guard vecgen.append u32 allocs.bump_bytes v 102 . else return 4 .
let x option u32 . be vecgen.at u32 allocs.bump_bytes v 0 .

Types are written every time. Not for lack of inference — it was deliberately left out. The source shows which instances were created (how many copies of code end up in the binary), and readers know v’s element type without tracing back to its declaration. Inference can be added later; the reverse is hard. For bytes only, growvec, which takes no type arguments at all, is shorter.

Design. ① Monomorphisation — vec u32 becomes a concrete struct at compile time, with every size and offset a constant. The price is code per instance, a visible cost, not a hidden one. ② Storage is bytes — because allocators hand out bytes. view_array presents them as elements, and size_of t answers how many bytes are needed. Without it, generic code would reserve the maximum width (8 bytes) and vec u8 would use 8×. ③ Rights come from elsewhere — the container does not create rights, it only carries them.

opeffectsWhat it does
vec—struct — al a · store mut slice u8 · n u64 (number of elements held)
open t cap0 (using al)state via aOpens an empty vector (room for cap0 elements). none if the first place cannot be obtained
count_of t a g · cap_of t a gnoneElements held · current capacity (in elements)
append t a g xstate via aOne element — grows to double + 8 elements when short. false = no new place
at t a g inoneThe i-th element → option t
set_at t a g i xnoneChanges the i-th. false out of range — does not silently grow
reserve_more t a g nstate via aReserves room for n more elements ahead
view_of t a gnoneA slice t over only what is held

Table 50.1 — Ops of vecgen — the first two arguments are element type and allocator type (open takes only the element type)

Units are not mixed — cap0, cap_of, count_of, at and set_at all count elements; bytes never leave the module. Elements are sized scalars or viewable structs (every field has a byte layout, 255 bytes total or less) — at returns such a struct with its fields intact. The result of view_of can go straight to sortgen or searchlib.

Reserving first gives all-or-nothing. After reserve_more succeeds, the following appends cannot fail midway.

guard vecgen.reserve_more u16 allocs.bump_bytes v (len src) . else return false .
var i u64 be 0 .
while lt i (len src) . do
  guard vecgen.append u16 allocs.bump_bytes v (index src i) . else return false .
  set i (add i 1) .
end

Counter-example. Using an old view after growth

Holding s from view_of and calling append may change the storage, leaving s looking at the old buffer. If it happened not to grow, it is right by accident, which is worse. Take views last.

Counter-example. Ignoring append’s false · reading up to cap_of

false is OOM; ignoring it leaves a silently shorter vector. Past the count is garbage — use count_of or view_of.

Counter-example. Structs without layout · nesting

Opening with a struct that has a slice field fails because there is no byte layout and so no array (E-IR-UNDEF). There is no nesting like vec (vec u8 …) either — a type argument is one word.

Cautions. No individual release or shrinking — a bump allocator only releases wholesale, so reduce growth with cap0 or reserve_more. A vector lives within its allocator’s lifetime. Owning elements (vec files.handle) currently fail inside the instance, and the diagnostic does not point at the call site — a remaining defect. The caller-held-buffer container is separate, vecs — the ownership model differs, so they were not folded together.