The Zero-GC Memory Model: Arenas & Pools
Ube completely eliminates Garbage Collection pauses without imposing Rust's lifetime annotations ('a). Memory safety is achieved through deterministic bump arenas, explicit boundary promotion, and generational handle pools.
1. The Zero-GC Philosophy
Garbage-collected languages (Go, Java, C#) suffer from unpredictable p99 latency spikes and stop-the-world pauses that disrupt 60 FPS / 120 FPS game rendering loops and audio pipelines. Manual memory management (C, C++) invites memory leaks, buffer overflows, and use-after-free exploits.
Ube solves this with three distinct tiers:
- Stack: Fixed-size primitives, arrays, and value structs.
- Regional Bump Arenas (
arena { }): Dynamic collections and strings whose memory is reclaimed in 1 single CPU instruction. - Generational Pools (
Pool[T]): Dynamic entities and graphs with compile-time affine ownership.
2. Regional Arenas (arena { ... })
An arena block establishes a new lexical bump allocator. Every heap allocation (such as dynamic slices []T, string builder buffers, and newly formatted strings) made inside the block is allocated from this child arena.
When execution exits the block, the entire scratch memory is reclaimed instantly by rewinding the bump pointer:
fn main() {
println("Entering main arena scope...")
// Statement Form:
arena {
let mut scratch_items = []i32{}
for i in 0..1000 {
scratch_items.push(i * 10)
}
println(f"Processed {scratch_items.len} items")
} // <-- scratch_items memory is reclaimed instantly here!
println("Exited arena: zero garbage left behind.")
}
Arenas can also be used as expressions, returning a computed value:
fn compute_summary() -> i32 {
let total = arena {
let mut data = []i32{10, 20, 30}
data[0] + data[1] + data[2] // Primitive integers are flat value types!
}
return total
}
3. The Upward Reference Invariant
Because child arenas are wiped before outer regions, an inner arena must never store data into an outer variable without promoting it. The Ube compiler enforces this strictly at compile time:
fn leak_attempt() {
let mut outer_list = []i32{}
arena {
// COMPILE ERROR: Outer slice mutation attempted within inner arena
// outer_list.push(42)
// COMPILE ERROR: Assigning inner-allocated data to outer variable
// outer_list = []i32{1, 2, 3}
}
}
promote(...).
4. The promote(value) Primitive
To return or transfer heap-backed data (such as slices or strings) out of an arena block, call promote(value). Promotion performs an explicit deep copy of the value into the parent region's arena:
fn load_active_ids() -> []i32 {
let result = arena {
let mut temp = []i32{}
temp.push(101)
temp.push(102)
temp.push(103)
// Deep copies the slice into the parent arena:
promote(temp)
} // temp's scratch memory is freed; promoted slice survives!
return result
}
fn main() {
let ids = load_active_ids()
println(f"Loaded {ids.len} IDs: first={ids[0]}")
}
5. Generational Entity Pools (Pool[T])
In games and UI applications, game objects and entities (enemies, projectiles, particles) are constantly spawned and destroyed in arbitrary order. Bump arenas are unsuitable for arbitrary per-entity lifecycles.
For this domain, Ube provides Pool[T]—a high-performance, generational slot allocator:
struct Enemy {
id: i32,
hp: i32,
active: bool
}
fn main() {
// 1. Create a pool with 128 initial slots
let mut enemies = Pool[Enemy].new(128)
// 2. Spawn an entity and receive a generational handle
let boss_handle = enemies.spawn(Enemy {
id: 1,
hp: 500,
active: true
})
// 3. Retrieve entity using generational lookup (returns Enemy?)
if let e = enemies.get(boss_handle) {
println(f"Boss #{e.id} is alive with {e.hp} HP")
}
// 4. Despawn entity linearly
enemies.despawn(boss_handle)
// 5. Attempting to use boss_handle again:
// enemies.get(boss_handle)
// ^-- COMPILE ERROR: Use of moved variable 'boss_handle'
}
null) rather than reading corrupted memory.
6. Affine Move Semantics
Non-copy types in Ube (such as string, dynamic slices []T, and Pool[T] handles) have single ownership. Assigning them transfers ownership (move):
fn main() {
let a = "Ube Core String"
let b = a // Ownership of string moved to 'b'
// println(a) // COMPILE ERROR: use of moved variable 'a'
println(b) // OK
let mut message = "first"
let snapshot = message // 'message' moved
message = "second" // Reassignment reactivates 'message'
println(message) // OK
}