A New Way to Think About Borrow Checking (In Reverse)
本文为原文前 6,000 字符的节选翻译,完整内容请查看原文。
A New Way to Think About Borrow Checking (In Reverse)
Travel backwards in time, so that you already know the future. Oct 18, 2026 — Evan Ovadia. Have you ever had a weird idea, and then it worked, but you can’t really believe it? That’s what I’m feeling right now, about Valen’s borrow checker. Because it travels backward through time. Or, to be less dramatic, it walks functions backwards, which is still very weird.
穿越回过去,这样你就已经预知了未来。2026年10月18日 — Evan Ovadia。你有没有过这样的经历:产生了一个奇怪的想法,结果它竟然成功了,但你却难以置信?这就是我现在对 Valen 借用检查器的感受。因为它在时间中逆向穿行。或者说得没那么戏剧化一点,它是在逆向遍历函数,这依然非常奇怪。
Years ago, if you told me it would be good to iterate a function in reverse, I would have called you crazy. It turns out, there’s actually some really good reasons to do this, and it’s fairly common when you’re doing e.g. optimization passes. But I don’t think anyone’s done this for checking memory safety. If you want a really deep look into the world of borrow checking, read on! We’re about to see some weird things.
几年前,如果你告诉我逆向迭代函数会很有用,我会觉得你疯了。事实证明,这样做确实有一些非常好的理由,而且在进行优化遍历等操作时,这相当常见。但我认为还没有人将其用于内存安全检查。如果你想深入了解借用检查的世界,请继续阅读!我们将看到一些奇怪的事情。
This is a pretty deep under-the-hood look into borrow checking algorithms. If you want something epic and entertaining instead, check out my recent post on The Golden Spike, and Resurrecting the Vale(n) Programming Language!
这是一篇关于借用检查算法的深度底层剖析。如果你想看一些更宏大、更有趣的内容,请查看我最近关于“The Golden Spike”以及“复活 Vale(n) 编程语言”的文章!
A weird kind of borrow checker. This isn’t going to be your normal shared-xor-mutable borrow checking. This is Valen’s borrow checker, the one that makes shared-xor-mutable optional, and can call into Rust functions with memory safety across the boundary. Valen is still very experimental. Many things have holes and sharp edges, and likely will until v0.1 comes sometime in February. The glorious months-long refactoring spree is already underway (hence this new borrow checker!). Feel free to check out the Valen repo and beware, here be dragons!
一种奇怪的借用检查器。这不会是你通常所见的“共享异或可变”(shared-xor-mutable)借用检查。这是 Valen 的借用检查器,它使“共享异或可变”成为可选,并且能够跨边界调用具有内存安全的 Rust 函数。Valen 仍处于非常实验性的阶段。许多地方还存在漏洞和缺陷,这种情况可能会持续到二月份 v0.1 版本发布。长达数月的重构狂潮已经开始(这就是这个新借用检查器的由来!)。欢迎查看 Valen 仓库,但要小心,前方有龙!
…and a weird, backwards implementation! Valen compiles a function in four phases: It parses the function, turning source text into an AST. It does static typing, figuring out the type for every variable, argument, and expression. It does static grouping, where every reference knows what variable (or what part of what variable) it’s pointing at. It safety-checks the function, to make sure that a reference isn’t used after it’s invalidated. I’ll explain all of this further below. The weirdest part is that #4 happens in reverse. It looks at the last statement in the function, and then walks backwards until it reaches the top of the function. This is weird because, as far as I know, it’s the only borrow checker whose safety check goes backwards.
……以及一种奇怪的逆向实现!Valen 分四个阶段编译函数:它解析函数,将源代码转换为抽象语法树(AST)。它进行静态类型检查,确定每个变量、参数和表达式的类型。它进行静态分组,使每个引用都知道它指向哪个变量(或变量的哪一部分)。它对函数进行安全检查,确保引用在失效后不会被使用。我将在下文进一步解释所有这些内容。最奇怪的部分是第 4 步是逆向进行的。它查看函数的最后一条语句,然后向后遍历,直到到达函数的顶部。这很奇怪,因为据我所知,它是唯一一个安全检查采用逆向方式的借用检查器。
Phase 2: Static Typing. The static typing pass will be familiar to some of you, it assigns types to variables. For example, it turns this program: valen exported func main() { let fuel = 5; let force = fuel; } into this program: valen exported func main() { let fuel i64 = 5; let force i64 = fuel; } This is pretty normal, lots of compilers do it. Compilers usually do this as a forward pass, walking from the top of the function to the bottom, figuring out types as it goes. We’re going to do something similar but weirder in phase 3; phase 3 is going to do static grouping.
第二阶段:静态类型检查。静态类型检查过程对你们中的一些人来说会很熟悉,它为变量分配类型。例如,它将这个程序:valen exported func main() { let fuel = 5; let force = fuel; } 转换为这个程序:valen exported func main() { let fuel i64 = 5; let force i64 = fuel; } 这很正常,许多编译器都会这样做。编译器通常将其作为前向遍历来执行,从函数顶部遍历到底部,并在过程中确定类型。我们将在第三阶段做类似但更奇怪的事情;第三阶段将进行静态分组。
Phase 3: Static Grouping. This is another forward pass, which figures out where each reference points to. For example, in this program: valen func print_first_fuel_cell(s &Ship) { let first_ref = &s.fuel_cells[0]; print_int(first_ref); } Static grouping figures out that first_ref is pointing in s.fuel_cells[]: valen func print_first_fuel_cell(s &Ship) { // Static typing says: first_ref &int // Static grouping says: first_ref &int in s.fuel_cells[] let first_ref = &s.fuel_cells[0]; print_int(first_ref); } In other words, it’s pointing to an int somewhere in the s.fuel_cells[] array. This information is now part of the variable’s type. We’ll be using this information in the next phase, which actually checks for use-after-free.
第三阶段:静态分组。这是另一个前向遍历,用于确定每个引用指向哪里。例如,在这个程序中:valen func print_first_fuel_cell(s &Ship) { let first_ref = &s.fuel_cells[0]; print_int(first_ref); } 静态分组确定 first_ref 指向 s.fuel_cells[]:valen func print_first_fuel_cell(s &Ship) { // 静态类型检查说:first_ref &int // 静态分组说:first_ref &int 在 s.fuel_cells[] 中 let first_ref = &s.fuel_cells[0]; print_int(first_ref); } 换句话说,它指向 s.fuel_cells[] 数组中的某个 int。这些信息现在是变量类型的一部分。我们将在下一阶段使用这些信息,该阶段将实际检查释放后使用(use-after-free)问题。