SkyHetu:在 Rust 中设计因果优先编程语言
Source: Dev.to

我职业生涯中遇到的大多数 bug 都可以归结为一个问题:“这个变量是怎么来到这里的?”
现代编程语言是失忆症患者。如果 user.balance 为 0,应用只知道它现在是 0,但并不记得导致该状态的事件序列。是逻辑错误?竞争条件?还是一次意外的函数调用?为了弄清楚,我们会添加打印语句、附加调试器,并在脑海中尝试回放历史。
我决定做点不同的事。我构建了 SkyHetu,一门把 因果关系 视为一等公民的编程语言。
哲学转变
在 SkyHetu 中,你不仅仅是改变状态;你是在创建一个事件。为此,我强制了三条激进的设计决策:
- 默认不可变性: 除非你明确请求,否则不能修改变量。
- 显式状态: 可变变量使用
state声明,而不是let。 - 箭头运算符 (
->): 赋值 (=) 用于初始化,箭头 (->) 用于变异。
state counter = 0
counter -> counter + 1
-> 运算符做了两件事:
- 更新值。
- 记录因果关系。
用 Rust 构建时间机器
要实现这一点,我需要对内存和执行流进行高性能、底层的控制,于是选择了 Rust。SkyHetu 的核心是一个基于栈的虚拟机(VM)。在栈和堆旁边还有 CausalityLog。
pub struct CausalityLog {
history: HashMap>,
clock: usize, // Logical time
}
pub struct MutationEvent {
pub old_value: Value,
pub new_value: Value,
pub timestamp: usize,
pub location: Option,
}
每当 VM 执行 OP_TRANSITION 指令(由 -> 触发)时,它会捕获 前后 值的快照,用逻辑时钟打上时间戳,并将其推入历史记录。
“侦探板”可视化
因为语言记得一切,调试不再像考古,而更像侦探工作。我实现了一个原生函数 causal_graph(),可以把内部历史导出为 DOT 格式(用于 Graphviz)或 JSON。
state score = 100
score -> score - 10 // event 1
score -> score - 50 // event 2
print(causal_graph("score", "dot"))
生成的图把每个状态显示为节点,把每个带时间戳的转变显示为边——相当于代码的“侦探板”。
为什么选 Rust?
Rust 对此任务非常合适,原因有三:
- 枚举(Enums): 表示动态类型(
Value::Number,Value::String)既简单又安全。 - 性能: 跟踪因果关系会有额外开销,但 Rust 的零成本抽象让我能够优化关键路径。
- 正确性: 编写垃圾回收器和 VM 本身就很复杂。借用检查器帮我避免了无数本该在 C++ 实现中出现的段错误。
结果
SkyHetu 不只是玩具;它可以编译模块、处理闭包,甚至支持类。与其他语言不同的是,它内置了自省功能。你可以问语言:“为什么 X 为真?” 并得到一条因果链。
print(why(counter))
// Causality chain for 'counter':
// 1. [t=1] 0 -> 1
// 2. [t=2] 1 -> 2
我们把 90 % 的时间花在调试上。也许是时候让我们的语言帮助我们处理剩下的 10 % 了。