Memory Overview

Current implementation

The compiler currently implements scope-based automatic cleanup for strings and structs with string fields. All other heap memory is manual (mem.alloc / mem.free + defer).

Managed types

The compiler classifies locals as “managed” if they are:

  • String type (dynamic string descriptors)
  • Structs that contain string fields

Managed locals get automatic cleanup when they go out of scope.

Scope-based cleanup

tape
fn process() {
    let name = build_name();    // managed string
    let data = read_input();    // managed string
    // both are freed automatically at scope exit
}

The compiler inserts string_drop calls at every scope exit point (end of block, return, early return). Drop order is reverse declaration order.

Copy semantics

Assigning a managed value to a new variable produces a deep copy:

tape
let a = "hello";
let b = a;  // b is an independent copy (deep copy if dynamic)

Static strings (string literals, cap == 0) are shallow-copied (just the pointer). Dynamic strings (built at runtime, cap > 0) are heap-duplicated.

Move on return

When a function returns a managed local, the compiler skips the drop for that local — the value is “moved” to the caller:

tape
fn make_greeting(name: *u8) -> []u8 {
    let s = str.concat("Hello, ", name);
    return s;  // no drop — ownership transfers to caller
}

Manual allocation

For raw memory, use the mem module:

tape
import mem from "mem";

let ptr: *u8 = mem.alloc(1024);
defer mem.free(ptr, 1024);

Available functions:

FunctionDescription
mem.alloc(size: u64) -> *u8Allocate bytes
mem.free(ptr: *u8, size: u64)Free allocation
mem.set(dst, val, count)Fill memory
mem.copy(dst, src, count)Copy bytes (non-overlapping)
mem.move(dst, src, count)Copy bytes (overlapping safe)
mem.zero(dst, count)Zero-fill
mem.compare(a, b, count) -> i32Compare bytes
mem.equal(a, b, count) -> boolEquality check

Defer for cleanup

Use defer to schedule cleanup for manual resources:

tape
let file = os.open("data.bin");
defer os.close(file);

let buf: *u8 = mem.alloc(4096);
defer mem.free(buf, 4096);

See Defer & Errdefer for details.

PlannedThe design specifies profile-based memory strategies: t0 fully manual (no implicit heap), t1 scope-based with explicit ownership, t2 garbage collected. Currently all profiles use the same scope-based string cleanup with no GC. Profile-differentiated memory management is not yet implemented.

Last modified: