Scattered Thoughts 放出了一篇很硬的语言实验:一门 toy language,没有静态类型检查,却在运行时做借用检查。目标很明确:把动态类型、值语义、栈分配、内部指针、单一所有权和受限借用尽量装进同一套模型里。
我觉得它值得看,不是因为又多了一门语言,而是因为它对准了动态语言一直没处理干净的问题:既想保留 REPL、运行时代码生成、胶水代码的灵活性,又不想为复制、GC 停顿、RC+COW,或者完整静态类型系统的复杂度,把账一次性交完。
作者自己也很克制。这不是工业方案,也不是 Rust 翻版。连 soundness,作者都只说自己“至少有 60% 把握”。所以这篇该看的,不是“能不能立刻拿来用”,而是“这条设计路线有没有抓住真问题”。
这套方案到底做了什么
它的出发点很朴素:默认变量是独立值。你改一个,不该偷偷影响另一个。这就是值语义。
问题也很老:值一旦变大,处处复制就贵了。于是作者引入三类引用,把“值”和“别名”分开管。
| 引用类别 | 写法 | 含义 | 主要限制 |
|---|---|---|---|
| owned | ^ move | 转移所有权 | 原位置失效,可修改 |
| borrowed | ! borrow | 临时独占借出 | 借出期间不能再借,也不能移动 |
| shared | & share | 共享只读视图 | 可多个并存,但都不能写 |
几个锚点必须说清,不然很容易误读成“动态版 Rust”:
- 借用检查发生在运行时,不是编译期。
- 引用计数只存在于动态类型函数帧里,而且放在栈上。
- 它不跨线程,所以不需要原子操作。
- 违反规则时,运行时会立刻报错,并指向具体值和借用关系。
- boxed 引用不能指向 borrowed 或 shared。
- 借出期间,值不能再被借出,也不能被 move。
- 部分 move 之后,整个值都视为失效,除非整体重建。
- 生命周期不能逃逸局部作用域;块里借来的引用,不能带出块外。
这意味着什么?它不是“完全不检查类型也能免费拿到借用安全”。它只是把一部分本该在编译期做的约束,转到了运行时,再用表达力限制把复杂度压住。
换来的收益也很具体:它试图让动态语言保住可变值语义,又不必把所有大对象都交给复制、GC 或堆分配。内部指针、外部迭代器这类普通动态语言很难优雅处理的东西,这里至少有了可表达的空间。
它解决了什么,又牺牲了什么
真正该比的,不是“像不像 Rust”,而是它在几条老路线之间怎么换账。
| 路线 | 能得到什么 | 主要代价 | 这次实验的位置 |
|---|---|---|---|
| Rust 式静态借用检查 | 强安全、低运行时开销 | 类型系统重,动态场景不顺手 | 更弱,借用能力受限,也不等价 |
| Julia / Zig 式动静分层 | 在动态与静态间切换 | 边界设计难,心智模型容易裂开 | 这次也在探索这条分层问题 |
| RC + COW | 对动态语言更友好 | 计数和复制开销重,内部指针难做 | 这次就是想绕开这笔成本 |
| 纯 GC / 引用语义 | 用起来省心 | 值语义弱,别名与性能经常打架 | 这次明确不走这条 |
所以我更看重的,不是“运行时借用检查”这五个字,而是它正面碰了“可变值语义”这块硬骨头。
很多动态语言在这里会滑过去。表面上给你灵活,背后要么偷偷共享引用,要么靠复制保值语义,要么把性能和内存账塞给运行时。用的人一开始很舒服,规模一大就开始付利息。
“天下熙熙,皆为利来。”语言设计也一样。你想同时拿到动态性、值语义、内部指针和低复制,就得付账。这里的账单不是类型标注,而是运行时检查、栈上计数、不可逃逸、装箱限制,以及更收紧的借用表达力。
这也是它和 Rust 的边界。Rust 把更多事放到编译期解决,所以运行时更轻,表达能力也更完整。这个实验则反过来:编译期少做,运行时多兜,但为了不失控,又把规则收得更紧。它比 second-class references 更强,但离 Rust 还有明显距离。
我也不认同把它说成“完全不需要类型系统”。原文讨论的,其实是动态与静态如何分层切换,不是宣布类型系统失效。把它读成“类型系统没必要了”,那就读偏了。
谁该关心,接下来该看什么
这件事最该让两类人停一下。
一类是关注 Rust、Julia、Zig 语义取舍的语言设计者和底层开发者。你现在不需要迁移,也没东西可迁。但你该把它当成一个设计样本:动态语言是不是只能在“全靠 GC”和“全上静态类型”之间选边站。做解释器、脚本宿主、嵌入式 DSL 的人,尤其值得看它怎么处理值语义和内部指针。
另一类是对动态语言性能、安全性和可变值语义很敏感的系统工程师。短期内,这不影响采购,也不影响生产选型。更现实的动作是:先观望,不跟风;如果你在做 REPL、热重载、运行时代码生成、插件宿主或胶水层 API,可以拿它当参考,检查自己现在到底是靠复制、GC,还是靠约定俗成在撑语义。
现在最该盯的,不是 demo 多会做题,而是它能不能跨过三道坎:
- 闭包、容器和模块边界一复杂,报错还能不能保持可解释。
- 动态帧里的运行时计数,到了真实程序规模后还便不便宜。
- 动态与静态的切换边界,能不能做顺,不把开发者逼成两套心智模型。
这些问题没有一个是装饰性的。很多语言设计不是死在原理上,而是死在成本表上。铁路、报业、平台都演过同一出戏:账在样板间里很好看,进了工程现场就变形。这件事也一样。
所以我的判断很简单:这更像一条该认真追踪的语言设计路线,不像一颗马上能落地的通用解药。它抓住了真矛盾,但它给出的只是新账单,不是免单。
