ZZust

Memory Management

没有 GC,换来可预测的释放边界

Zust 的目标不是复刻带垃圾回收的动态语言,而是在脚本便利性和服务端长期运行之间取一个更直接的点:临时对象跟随函数调用结束,真正有用的数据才离开调用现场。

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

无 GC 停顿

运行时不需要等待全局扫描来决定什么时候回收临时对象。

调用结束即清理

函数里的中间 list、map、字符串和 Dynamic 值随着调用边界释放。

适合长期服务

请求和任务反复执行时,临时状态不会因为遗漏引用而不断堆积。

为什么不做 GC

GC 的优势是省心,但代价是回收时机不完全由当前代码决定。Zust 更关心长期运行服务里的确定性行为:一次脚本调用结束后,临时对象应该在一个清楚的边界被释放,而不是等待未来某次全局回收。

这让调用成本更容易估算,也让服务端调试更直接。内存增长时,开发者优先检查哪些值被显式放进 ROOT、缓存或宿主对象,而不是先猜 GC 什么时候会运行。

线程 VM 局部存储

Zust VM 在线程内维护局部存储。脚本运行期间创建的 Dynamic 值、临时 list/map、字符串和中间计算结果,都优先落在当前线程 VM 的调用局部区域。

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

这个模型刻意简单:线程之间不共享一堆隐式临时对象,函数调用也不把所有中间值都变成长期状态。

函数调用生命周期

一次函数调用开始时,VM 建立调用局部区域。函数内部可以自由创建临时对象。调用结束时,返回值和被显式保存的对象会被移出或挂到目标位置,其余临时对象整体释放。

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

// parsed 等中间对象在调用结束后释放
// result 被作为返回值移出调用边界

有用的拿出来,临时的释放

如果一个值需要活过当前函数,它必须变成返回值、写入 ROOT、进入数据库或交给宿主 Rust 持有。没有被这样明确带走的值,就被视为临时对象。

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 进入 ROOT
// scratch 调用结束后释放

长期运行服务里的价值

服务端脚本经常被 HTTP、WebSocket、console 或任务系统反复调用。如果每次请求产生的临时对象都依赖隐式生命周期,内存泄漏会很难定位。Zust 把“调用结束就释放临时对象”做成默认行为,能解决绝大多数因为临时结果、解析结果和中间响应对象堆积造成的问题。

这尤其适合 agent 生成脚本和热更新脚本:生成代码可以保持短、直接,不必为了每个临时 list/map 写复杂释放逻辑。

边界也要清楚

这种设计不会替开发者管理所有长期状态。写入 ROOT、缓存、数据库连接、文件句柄、网络连接或宿主 Rust 对象的内容,仍然需要按它们自己的生命周期管理。Zust 解决的是脚本调用内部大量临时对象的释放问题,而不是把所有资源都变成魔法自动清理。