Dynamic Arrays
Fixed arrays are great when you know the size upfront. When you don’t, array<T> grows as needed.
Creating a Dynamic Array
fn main() {
var list:array<i32>
list.init()
list.push(10)
list.push(20)
list.push(30)
print("Length: {list.len()}\n") // 3
for (i = 0:list.len() - 1) {
print("list[{i}] = {list.at(i)}\n")
}
list.free()
}
Three steps: init() to set up, use it, free() when done. No garbage collector — you own the memory.
Push and Access
fn main() {
var names:array<str>
names.init()
names.push("Alice")
names.push("Bob")
names.push("Charlie")
names.push("Diana")
print("First: {names.at(0)}\n")
print("Last: {names.at(names.len() - 1)}\n")
print("Count: {names.len()}\n")
names.free()
}
Use .at(index) to read elements and .len() for the count.
Iterating
For-each works with dynamic arrays:
fn main() {
var scores:array<i32>
scores.init()
scores.push(95)
scores.push(87)
scores.push(92)
scores.push(78)
var total = 0
for (var s in scores) {
total += s
}
print("Average: {total / scores.len()}\n")
scores.free()
}
Sorting
Built-in sort for numeric arrays:
fn main() {
var nums:array<i32>
nums.init()
nums.push(42)
nums.push(17)
nums.push(93)
nums.push(5)
nums.push(68)
print("Before: ")
for (var n in nums) {
print("{n} ")
}
print("\n")
nums.sort()
print("After: ")
for (var n in nums) {
print("{n} ")
}
print("\n")
nums.free()
}
Building with Collect
Remember collect from the loops tutorial? It returns a dynamic array:
fn main() {
var data:i32[8] = {1, 2, 3, 4, 5, 6, 7, 8}
var evens = for (n in data) where (n % 2 == 0) collect n
print("Evens: ")
for (var e in evens) {
print("{e} ")
}
print("\n")
evens.free() // don't forget!
}
Fixed vs Dynamic: When to Use Which
| Use case | Choose |
|---|---|
| Size known at compile time | i32[10] (fixed) |
| Size known at startup | array<T> |
| Growing collection | array<T> |
| Stack-allocated, fast | i32[N] (fixed) |
| Return from function | array<T> |
fn main() {
// Fixed: perfect when you know the size
var rgb:f32[3] = {0.2, 0.5, 0.8}
// Dynamic: when size varies
var results:array<i32>
results.init()
for (i = 1:100) {
if (i % 7 == 0) {
results.push(i)
}
}
print("Multiples of 7 under 100: {results.len()}\n")
results.free()
}
Try it — Build a dynamic array with
collectin the Playground.
Expert Corner
array<T> is a thin wrapper over a heap-allocated buffer. Internally it’s { T* data; int64_t len; int64_t cap; } — pointer, current length, and capacity. When you push and the buffer is full, it reallocates with double the capacity. This gives amortized O(1) push performance.
Why explicit .init() and .free()? GX doesn’t run constructors or destructors behind your back. If you declare var list:array<i32>, the memory is uninitialized until you call .init(). This makes it crystal clear where allocation and deallocation happen. No RAII surprises, no hidden mallocs, no destructor chains — you see every allocation in the code.
collect generates .init() and .push() calls — it’s syntactic sugar, not magic. The compiler transforms var evens = for (n in data) where (...) collect n into an init, a loop with a push, and assigns the result. You still need to .free() the result.
Memory tip: If you know approximately how many elements you’ll add, you can avoid repeated reallocations. The current implementation doubles capacity on each grow, starting small. For performance-critical code, consider pre-sizing or using a fixed array when possible.