无需类型检查的借用检查
2026 年 4 月 22 日发布的这个动态类型玩具语言演示,支持内联值、栈分配、内部指针、单一所有权,以及一种有限形式的借用机制。它相较于 Rust 表达能力弱,但比二等引用强,例如可实现外部迭代器。由于无静态类型,借用操作需动态检查,而该演示能以低成本实现并给出有用错误信息,代码可查看。
背景
正在探索以 Julia 和 Zig 为代表的类型系统风格,这两种语言从动态类型系统起步,通过动态类型检查实施,再构建静态类型系统证明动态类型检查不必要。动态类型系统提供灵活性和便捷元编程能力,静态类型系统消除大部分代码开销。Julia 和 Zig 处理无法静态类型检查的代码方式不同,Zig 拒绝编译,Julia 保留动态类型检查并在获更多类型信息时再次运行静态类型检查。对于 Zest,探索的方案是代码可动态类型解释执行或静态类型编译,但切换需显式注解,目标是让大部分代码享受静态类型保障,同时可选择动态类型粘合代码处理多种情况。棘手的是还要强制实施可变值语义,目前主要实现策略有引用计数和写时复制、静态类型系统,但都有问题,因此尝试新方法,方案有借用检查开销限于特定操作、引用计数存栈上、不在线程间共享、仅在动态类型函数帧产生、违反规则运行时抛错指出出错值,且至少有 60% 把握方案可靠。
REPL
若看到绿色对勾,示例是交互式的,可编辑代码点击求值按钮查看结果;若看到红色叉号,可能是关闭 JavaScript 或未在浏览器测试,只能查看代码框末尾离线结果。
值
玩具语言精简,支持整数、元组、函数和基本控制流。每个变量是独立值,修改一个变量值不影响另一个。代码块结束时丢弃块中定义变量关联的值并释放内存。这种将值语义与可变性结合的方法简单易实现,但处理大值时每次创建完整副本不可行,需在不破坏值语义前提下实现值共享。
引用
引用可表达值存储在与父值不同位置的概念。创建引用可用 box 函数,将内容存堆上。解引用运算符 * 用于访问引用内部内容。复制 box 内容对大数据结构不适用,直接复制指针会破坏值语义假象,所以不允许复制 box,除非添加显式注解。使用引用有几种选择,一是用 ^ 移动值,得到引用副本但销毁原始引用;二是用 ! 创建借用引用,新引用丢弃时值返回原始位置;三是用 & 创建共享引用,原始所有者保留副本不销毁,为维持值独立假象,不允许修改副本。
闭包
该语言支持闭包,但闭包不隐式捕获作用域中变量,示例无法正常工作。可通过返回包含必要状态的元组并调用 next 函数实现,闭包是这种模式的语法糖,指定捕获状态和访问类型,编译器会转换。
安全性
底层借用和共享引用实现为指向原始值的指针,借用检查目标是兼顾值语义简单性和引用语义性能。用静态类型系统确保操作不破坏假象较容易,用动态类型系统以低成本实现困难,此方案有很大局限性。最大限制是拥有所有权的引用不允许指向借用/共享引用,确保借用/共享引用在栈上。当借用引用指向值时,不能为该值创建更多借用/共享引用,但运行时会跟踪借用引用丢弃情况。可解构借用引用为值的部分创建多个借用引用。当共享引用指向值时,只能为该值创建共享引用。存在借用/共享引用时,不能从变量中移动值。值被移动后,在整个值被替换前不能使用。变量只能持有指向生命周期更长变量的借用/共享引用。