ZZust

Memory Management

No GC, deterministic cleanup boundaries

Zust does not try to copy a garbage-collected dynamic language. It keeps scripting convenient while giving long-running servers a direct rule: temporary values live inside the function call unless they are explicitly moved out.

thread VM
  local store
    call frame
      Dynamic temporaries
      return value
    release frame locals

No GC pauses

The runtime does not wait for a global scan to decide when call-local temporary objects can be reclaimed.

Cleanup at call end

Intermediate lists, maps, strings, and Dynamic values are released when the function call finishes.

Built for services

Repeated requests and tasks do not keep accumulating temporary state by accident.

Why no GC

GC makes many programs easier, but it also moves cleanup timing away from the current piece of code. Zust is more interested in deterministic server behavior: after a script call finishes, temporary objects should disappear at a clear boundary instead of waiting for a later global collection.

When memory grows, developers can first inspect values explicitly stored in ROOT, caches, host objects, or databases instead of guessing when a collector will run.

Thread VM local storage

A Zust VM keeps local storage per thread. Dynamic values, temporary lists and maps, strings, and intermediate results created during script execution are allocated in the current thread VM call-local area.

thread_vm.local_store
  .push_call()
  .alloc_dynamic(value)
  .finish_call()

The model is intentionally plain: threads do not share a hidden pile of temporary objects, and function calls do not silently turn every intermediate value into long-lived state.

Function call lifetime

When a call starts, the VM opens a local call area. The function can allocate temporary objects freely. When the call ends, the return value and explicitly saved values are moved out; the remaining local temporaries are released together.

call handler(req)
  let parsed = parse(req.body)
  let result = build_response(parsed)
  return result

// parsed is released after the call
// result moves across the call boundary

Move useful data out

If a value must live beyond the current function, it has to become a return value, enter ROOT, go to a database, or be handed to host Rust. Everything else is treated as temporary.

let session = {user_id: user.id, login_at: now()};
root::add("sessions/" + session.user_id, session);

let scratch = [];
scratch.push("only used during this call");

// session enters ROOT
// scratch is released after the call

Why it helps long-running services

Server scripts are called repeatedly by HTTP, WebSocket, console, and task systems. If each request leaves behind parser results, intermediate lists, or response-building objects, leaks become hard to locate. Zust makes call-end cleanup the default, which solves most leaks caused by accumulated temporary script values.

This is especially useful for agent-generated and hot-updated scripts: generated code can stay short and direct without writing custom cleanup logic for every temporary list or map.

Clear boundaries

This design does not manage every long-lived resource for the developer. Values stored in ROOT, caches, database connections, file handles, network connections, or host Rust objects still need their own lifecycle rules. Zust solves call-local temporary cleanup, not every resource problem through magic.