Memory ModelSeen uses region-based memory management with no garbage collector. Memory is managed through regions, arenas, ownership semantics, and compile-time lifetime tracking.

Memory Model

Seen uses region-based memory management with no garbage collector. Memory is managed through regions, arenas, ownership semantics, and compile-time lifetime tracking.

Allocation Failure

Seen 0.9.2 treats allocation failure as part of memory safety. A program can set or inherit a process allocation budget with SEEN_MEMORY_LIMIT_BYTES; runtime allocation helpers track used bytes, peak bytes, and allocation failures.

Allocation-heavy APIs should prefer fallible forms that return Result<T, AllocError>:

import core.result.{Result, Ok}
import core.unit.{Unit}
import memory.allocation.{AllocError, ensureAllocationBudget, memoryStats}

fun reserveScratch(bytes: Int) r: Result<Unit, AllocError> {
    ensureAllocationBudget(bytes)?
    return Ok(Unit{})
}

Existing infallible APIs remain available for compatibility, but the runtime now routes core growth paths through budget-aware helpers and reports a Seen allocation failure diagnostic instead of relying on the host to OOM-kill the process.

Memory statistics

import memory.allocation.{setMemoryLimitBytes, memoryStats}

setMemoryLimitBytes(128 * 1024 * 1024)
let stats = memoryStats()
println("peak bytes: {stats.peakBytes}")

MemoryStats reports limitBytes, usedBytes, peakBytes, remainingBytes, and allocationFailures.

Stack vs Heap

Primitive types (Int, Float, Bool, Char) live on the stack. Classes are heap-allocated and accessed via handles (pointers stored as i64). Arrays use heap-allocated backing storage.

let x = 42          // stack
let name = "hello"  // string data on heap, handle on stack
let arr = [1, 2, 3] // array data on heap, handle on stack

Ownership

own -- exclusive ownership

fun processData(own data: Array<Int>) {
    // data is exclusively owned here
    // caller can no longer use it
}

move -- transfer ownership

let a = Array<Int>()
let b = move a  // ownership transferred to b
// a is no longer valid

borrow -- temporary read access

fun printData(borrow data: Array<Int>) {
    // read-only access, caller retains ownership
    for item in data {
        println("{item}")
    }
}

ref -- reference (pointer)

fun increment(ref counter: Int) {
    counter = counter + 1
}

inout -- mutable reference

fun swap(inout a: Int, inout b: Int) {
    let temp = a
    a = b
    b = temp
}

Regions

Regions define memory lifetimes for groups of allocations:

region gameFrame {
    let particles = Array<Particle>()
    // all allocations freed when region exits
}

Arena Allocators

Arenas provide bulk allocation with a single free:

arena {
    let nodes = Array<TreeNode>()
    for i in 0..1000 {
        nodes.push(TreeNode.new(i))
    }
    // all nodes freed at once when arena exits
}

Runtime Arena API

let a = seen_arena_new(1048576)     // 1MB arena
let idx = seen_arena_alloc(a, 64)   // allocate 64 bytes
let ptr = seen_arena_get(a, idx)    // convert index to pointer
seen_arena_reset(a)                  // bulk free
seen_arena_free(a)                   // release arena

Stack Regions

LIFO allocation for temporary data:

let region = seen_stack_region_new(4096)
let ptr = seen_stack_region_alloc(region, 32)
seen_stack_region_pop(region, 32)
seen_stack_region_destroy(region)

Pool Regions

Fixed-size block allocation for uniform objects:

let pool = seen_pool_region_new(64, 100)  // 64-byte blocks, 100 of them
let block = seen_pool_region_alloc(pool)
seen_pool_region_free(pool, block)
seen_pool_region_destroy(pool)

Memory-Mapped Regions

For file-backed memory:

let mapped = seen_mapped_new(path, size, flags)
let data = seen_mapped_data(mapped)
seen_mapped_sync(mapped)
seen_mapped_free(mapped)

Defer

Execute cleanup code when leaving a scope, regardless of how the scope exits:

fun processFile(path: String) r: String {
    let file = File.open(path)
    defer { file.close() }

    return file.readContent()
    // file.close() runs here automatically
}

errdefer

Only runs when scope exits via error:

fun allocateResources() r: Result<Handle, String> {
    let handle = acquire()
    errdefer { release(handle) }

    configure(handle)?  // if this fails, handle is released
    return Ok(handle)
}

Layout Control

@packed

Remove padding between struct fields:

@packed
class NetworkHeader {
    var version: Int
    var flags: Int
    var length: Int
}

@cache_line

Align to cache line boundary (typically 64 bytes):

@cache_line
class HotData {
    var counter: Int
}

@trivially_copyable

Mark a type as safe to memcpy:

@trivially_copyable
class Vec2 {
    var x: Float
    var y: Float
}

Safety Annotations

@move

Enforce move semantics on a type:

@move
class UniqueHandle {
    var fd: Int
}

Once moved, the original binding becomes invalid.

Architected in Kotlin. Rendered with Materia. Powered by Aether.
© 2026 Yousef.