Generic Containers and Ownership
Note: This post was drafted by an AI assistant under direction from the author. It is not first-hand writing; the design choices it describes are real, the prose explaining them is generated. Treat the technical content as the design talking, and the framing as a translation layer.
Vec(T), List(T), Map(K, V), Graph(T), and Pair(A, B) are type-parameterized containers. You pick the element type at the declaration; the container handles storage and bookkeeping.
Declaring a container type
typedef Vec(int) IntVec;
typedef List(Str) StrList;
typedef Map(Str, i32) Counts;If the element type itself contains a comma (most often a nested template), wrap it in T(...) so the preprocessor sees one argument:
typedef Vec(T(Pair(i32, Str))) PairVec;L vs R: who owns the value after insertion
Most insertion APIs come in two forms. The difference is what happens to the source after the call.
Scope(lt, DefaultAllocator) {
Vec(int) v = VecInit();
int x = 42;
VecPushBackL(&v, x); // L: container takes ownership of x
// x is now zero.
int y = 99;
VecPushBackR(&v, y); // R: container copies y
// y is still 99.
VecDeinit(&v);
}L is for move: the source is left empty. R is for copy: the source is unchanged.
For plain int, the practical difference is small. It matters when the element owns memory of its own — a Str, an Int, a nested Vec. With L, you avoid duplicating the inner allocation; with R, the container makes its own deep copy through a registered copy_init callback.
The unsuffixed form (VecPushBack, VecInsert, MapInsert) is an alias for the L form. Reach for R explicitly when you want to keep the source around.
A few rules to keep in mind
- L requires a writable l-value. A string literal or an arithmetic literal will not compile against
VecPushBackL; it needs storage the macro can zero. - R takes anything assignable. Literals,
const-qualified sources, expressions — all fine. - Container-owning element types still need a copy callback. When the element is a
Strand you want R to deep-copy instead of bit-copy, register acopy_init/copy_deinitpair at init time (VecInitWithDeepCopy).
Allocator
Every container carries an Allocator * field. You bind it at construction inside a Scope block — see Scope-Based Allocator Discipline for the full story. The container never destroys the allocator; the surrounding scope does.