Skip to content

Memory Model

XIOM uses ownership semantics for memory management. There is no garbage collector. Memory is freed when the owning binding leaves its scope.

The model uses lexical scope borrowing -- simpler than Rust's lifetime system: borrow validity follows scope nesting, use-after-free and double-free are ruled out by the model, and patterns that need escaping borrows use owned values instead. The borrow checker runs during compilation and reports violations with code E001; some cases are warnings rather than hard errors today, so treat every E001 as an error you must fix.

Ownership Rules

Rule Description
Single Owner Every value has exactly one owner at any time. Assignment moves ownership.
Scope Lifetime A value is freed when its owning binding leaves scope. No GC, no manual free.
Read Borrow (&T) Multiple simultaneous read borrows allowed. No mutation during active read borrows.
Write Borrow (&mut T) Exactly one write borrow at a time. No other borrows active simultaneously.
Explicit Clone Duplicating a value requires .clone(). No implicit deep copy.
Move On Call Passing a value to a function moves ownership unless the parameter is a borrow.

Borrowing

Borrows expire at the end of the block or statement they are created in. You can see when a borrow ends by looking at the braces.

fn consume(data: Vec[Int]) { }          // takes ownership
fn read_only(data: &Vec[Int]) { }       // read borrow
fn mutate(data: &mut Vec[Int]) { }      // write borrow

let v = [1, 2, 3];
read_only(&v);        // borrow, v still valid
mutate(&mut v);       // write borrow, v still valid  
consume(v);           // move -- v no longer usable here
// read_only(&v)     // COMPILE ERROR: v was moved

What the Model Cannot Handle

Borrows cannot be stored in struct fields. Borrows cannot be returned from functions (the compiler reports the return case as an E001 warning today). These patterns require owned types or explicit cloning.

This is a constraint, not a bug -- it is the design. Patterns that require escaping borrows are solved with owned types instead.

Clone

Use .clone() to explicitly duplicate a value:

let a = Vec.new();
a.push(1);

let b = a.clone();    // explicit deep copy
// a is still valid -- ownership was not moved

consume(a);           // a is moved
// b is still valid  -- it was cloned, not moved

Types with derive[Clone] get automatic clone implementations. Manual clone() methods can be written for types that need custom duplication logic.

Move Semantics

Passing a value to a function moves ownership:

fn take(v: Vec[Int]) {
  // v is owned here -- freed at end of scope
}

let data = [1, 2, 3];
take(data);           // ownership moves to take()
// data is no longer valid here

Use borrows when you don't need ownership:

fn inspect(v: &Vec[Int]) {
  // read-only access -- caller retains ownership
}

let data = [1, 2, 3];
inspect(&data);       // borrow -- data still valid
inspect(&data);       // can borrow again

Unsafe Blocks

Raw pointer arithmetic and C interop that cannot be verified by the compiler are permitted only inside explicit unsafe { } blocks.

unsafe {
  let raw: *Int = some_c_function();
  let value = *raw;   // dereference -- programmer guarantees validity
}

The unsafe keyword is a declaration that the programmer takes responsibility for memory safety inside that scope. Unsafe blocks are visible in code review and audits. They cannot be hidden.

Three rules matter when writing them:

  • Inline assembly (asm("...")) is only allowed inside an unsafe block.
  • A function whose entire body is a single unsafe block must declare requires: (the pre-entry contract); the compiler rejects it otherwise.
  • Safe functions cannot return a raw pointer (*T), and calling an extern "C" function from safe code is an error unless that function declares contracts.

The compiler guide lists the full confinement model and the C FFI guide shows the sanctioned wrapper patterns. Every unsafe block is a confined transaction with a guard heap, fault trapping, a once-only transient retry and zero-escape rules; the Unsafe guide documents all of it.

Differences from Rust

XIOM Rust
Lifetime annotations None Required for non-trivial cases
Borrow scope Lexical (visible by braces) NLL (non-lexical lifetimes)
Borrow in structs Not allowed Allowed with lifetime parameters
Return borrows Not allowed Allowed with lifetime parameters
Learning curve Moderate Steep