Fixed arrays
A fixed_array<T, N> is N elements of T, stored inline wherever the value lives. It does not grow, it does not allocate, and N is part of the type: fixed_array<int32, 4> and fixed_array<int32, 8> are unrelated.
fixed_array<int32, 4> $quad = [1, 2, 3, 4];
echo $quad->count(); // 4
echo $quad[0]; // 1
$quad[0] = 9;
echo $quad[0]; // 9array<T> is the one you want by default. Reach for this when the length is a fact about the type: a matrix, a colour, a stack buffer you do not want on the heap. A stable_array<T> grows without moving live slots.
N is a value
const usize N in a parameter list is a compile-time integer. Writing 4 at the use site binds it:
struct sized<const usize N>
{
const function n() : usize
{
return N;
}
}
sized<4> $a = sized<4>();
echo $a->n(); // 4A literal in that slot is typed as usize from the parameter. sized<4> and sized<8> intern as two types.
Literal length
The brackets still make a list. The destination says it is a fixed_array, so every element is an indexed write rather than an append, and the count has to match:
fixed_array<int32, 4> $quad = [1, 2, 3, 4];
echo $quad->count(); // 4fixed_array<int32, 4> $short = [1, 2, 3];
// error: this literal has 3 elements, 'fixed_array<int32,4>' has 4Leave the type off and [1, 2, 3, 4] is still an array<int32>. Nothing here changes that default.
There is no $quad[] = 5. It does not grow.
T[N] is the storage
The collection is a one-field struct over an inline array. You can write the storage type yourself when a field is all you need:
struct Transform
{
float32[16] $m;
}
Transform $t;
echo mem::size<Transform>(); // 64$t->m[0] is a place. Bounds on fixed_array live in its operators; indexing T[N] directly is the storage primitive, like mem::buffer::at.
A constructor would otherwise owe every slot. A field of fixed_array<T, N> or of T[N] starts as N zeros, the same value fixed_array<T, N>() builds, so leaving it unassigned is fine:
struct Packed
{
fixed_array<uint8, 4> $bytes;
constructor()
{
}
}
Packed $p = Packed();
echo $p->bytes[0]; // 0
echo $p->bytes->count(); // 4An int32 field still has to be assigned on every path. The zeros are a fact about the inline storage, not a general "uninitialized is zero" rule.
It iterates like an array
foreach has no idea what a fixed_array is. The type conforms to contract::iterable<T> by handing back a slice_iterator over ->sub():
fixed_array<int32, 4> $quad = [1, 2, 3, 4];
foreach ($quad as $n) {
echo $n;
}->sub() is written out, the same as on array<T>. A slice is a borrow.
Sorting
Same methods as an array: sort(), sort_unstable(), plus by: and key:. The length is the type, so there is no growing to think about. Sorting is the chapter.
fixed_array<int32, 4> $quad = [4, 1, 3, 2];
$quad->sort();
echo $quad[0]; // 1
echo $quad[3]; // 4C buffers
Every slot is live and N is the type, so there is no spare to commit. data() is the first element, or null when N is 0. Hand C that pointer and N.
fixed_array<int32, 4> $quad = [1, 2, 3, 4];
ptr<int32> $p = $quad->data();
$p:$[0] = 9;
echo $quad[0]; // 9
echo $quad->count(); // 4array<T> is the one that grows, and the one that needs spare / commit for the uninitialised tail.
It owns its elements
Copying one copies each element. Destroying one destroys each element. For a T that is just bytes, both are a memcpy of the whole blob.
fixed_array<string, 2> $words = ['a', 'b'];
fixed_array<string, 2> $copy = $words;
$copy[0] = 'c';
echo $words[0]; // a
echo $copy[0]; // c