No GC pauses
The runtime does not wait for a global scan to decide when call-local temporary objects can be reclaimed.
Memory Management
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
The runtime does not wait for a global scan to decide when call-local temporary objects can be reclaimed.
Intermediate lists, maps, strings, and Dynamic values are released when the function call finishes.
Repeated requests and tasks do not keep accumulating temporary state by accident.
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.
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.
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
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
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.
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.