金钉子:复活 Vale(n) 编程语言
摘要
这篇文章讨论了一个雄心勃勃的项目,旨在复兴Vale编程语言并创建一种名为Valen的新语言,目标是实现与Rust的无缝集成,以支持跨语言泛型和内存安全。
<p><a href="https://lobste.rs/s/fjnkn4/golden_spike_resurrecting_vale_n">评论</a></p>
查看缓存全文
缓存时间: 2026/09/17 15:38
# 金道钉与Vale(n)编程语言的复活
来源:https://verdagon.dev/blog/golden-spike-reviving-vale-valen
让我们做些极其雄心勃勃且极不理智的事
2026年9月17日——[](https://en.wikipedia.org/wiki/Golden_spike)
1869年5月10日曾有一个不可思议的时刻,铁路建设者们终于实现了**将东海岸铁路网与西海岸铁路网连接**的目标。那一刻,美国首条横贯大陆铁路诞生了。0 (https://verdagon.dev/blog/golden-spike-reviving-vale-valen#note0) 经过六年的努力,1 (https://verdagon.dev/blog/golden-spike-reviving-vale-valen#note1)两条铁路网终于在犹他州的普罗蒙特里峰交汇。为纪念这一刻,他们在铁路最后一根枕木上钉入了重17.6克拉的**金道钉**。
对我而言,“金道钉(https://en.wikipedia.org/Golden_spike)”这个短语意味着完成一项极其艰巨的任务,将两个独立而遥远的系统连接起来。2 (https://verdagon.dev/blog/golden-spike-reviving-vale-valen#note2) 在编译器领域,常常感觉每个编译器都与其他编译器*完全隔绝*。当一门语言需要调用另一门语言时,通常需要通过C ABI进行复杂的转换,编写“C绑定”(包装函数),有时还得与各路神灵做交易。即便如此,跨语言泛型3 (https://verdagon.dev/blog/golden-spike-reviving-vale-valen#note3)仍然无法实现(C没有泛型),且跨越边界绝对无法保证内存安全(因为是C!)。
**这就是为何不存在Rust++语言的原因;**在C边界上实现内存安全和泛型是不可能的。即使可能,集成两个编译器也*极其困难*。于是我暗自思忖,这*听起来像是2026年值得追求的目标!*
于是我们来到这里!本文将记录我这个*极其不自量力*的尝试的开端——创建一门**真正的类Rust++语言**,其编译器能与rustc无缝对话,使我们能够使用我的Rust图形库4 (https://verdagon.dev/blog/golden-spike-reviving-vale-valen#note4),并实现跨语言泛型、内存安全、线性类型、Nick Smith的组借用检查(https://verdagon.dev/blog/group-borrowing),以及其他众多诱人的特性。
暂且将这个新语言命名为“Valen”5 (https://verdagon.dev/blog/golden-spike-reviving-vale-valen#note5)6 (https://verdagon.dev/blog/golden-spike-reviving-vale-valen#note6),因为它与我现有的语言Vale (https://vale.dev/)相似(但已足够不同)。请继续阅读,本文将讲述迄今的探索历程!
整个尝试*极具实验性*,许多部分存在漏洞和粗糙边缘(参见旁注7 (https://verdagon.dev/blog/golden-spike-reviving-vale-valen#note7))。我将花费数月时间清洗、重写和巩固这个“恐怖之物”,然后才能将其推向世界。欢迎查阅源代码(https://github.com/valen-lang/valen),但请当心,此处有龙出没!
---
### 金道钉与Vale(n)编程语言的复活
0 本文0%由AI撰写。更多思考见此处(https://verdagon.dev/blog/personal-ai-policy)。我的立场:若想让人花费时间阅读,就请亲自花费时间写作。感谢阅读=)
1 姑且称之为*一团糟*。我最喜欢的部分(来源(https://cprr.org/Museum/Bowman_Last_Spike_CHS.html)):
“……在这日期前两天,联合太平洋铁路在皮德蒙特遭遇新麻烦:副总裁T·C·杜兰特博士乘坐的车厢被约400名工人(一名记者称500人)解挂并推至侧线,这些工人要求支付自1月1日起拖欠的工资。绝对的狠人!”
2 在Google Earth上,我曾将本地编辑应用连接至云存储,终于实现了在线编辑。我们将那个项目也称为“金道钉”。这次的Rust互操作探索让我回想起那个时刻!
3 下文将详细解释,但简而言之,能够使用`SomeRustStruct`。
4 这是一个基于awgpu(https://wgpu.rs/)的屏幕空间折射图形库,我称之为“Glass Domino”。天啊,我*真想*多聊聊它的工作原理,以及它如何高效实现重叠折射器。但要集中注意力,Evan!本文是关于编译器和语言的!
5 由于其编译器是`valenc`,我们可以将其读作“Valence”,听起来不错!
6 另外,对我而言很有趣的是,如今V、Val、Vale、Vala和Valen都已成为真实存在的语言。我有点想发布一个小型Rust互操作语言供他人使用,并命名为Va。
7 需要明确当前可工作的功能:
- Valen拥有线性类型,但尚不能声明现有Rust类型为线性类型。
- Valen支持组借用(闭包除外),且能跨边界进行借用检查。
- 结构体可跨边界工作,即使是实现了Rust trait的Valen结构体。但它们必须是零大小的(填充结构体在我的独立原型中可用,尚未移植到Valen)。
- 代际引用暂时禁用,有望不久后恢复。
8 特指绿龙。
---
### 一个愿望、一个希望与一门梦想语言
Rust是我最喜欢的语言之一9 (https://verdagon.dev/blog/golden-spike-reviving-vale-valen#note9),因其速度、安全性和生态……但有些特性若能改进,对我的用例来说将**更简洁且更完善**。我的愿望清单:
- 一个摆脱共享互斥性(https://verdagon.dev/blog/group-borrowing)的借用检查器
- 更快的运行时性能10 (https://verdagon.dev/blog/golden-spike-reviving-vale-valen#note10)
- 通过线性类型(https://www.youtube.com/watch?v=IpuvQUVB8Cg&t=2s)实现资源安全
- Zig风格的编译时计算(https://verdagon.dev/blog/impossible-optimization)11 (https://verdagon.dev/blog/golden-spike-reviving-vale-valen#note11)
- 代际引用(https://verdagon.dev/blog/generational-references)
- 无需`RefCell`或`Cell`即可持有可变数据的`Rc`
- ……以及众多其他特性12 (https://verdagon.dev/blog/golden-spike-reviving-vale-valen#note12)
但我不只是想要一门新语言,否则将无法访问Rust的库生态。那么我们如何兼顾这些,同时仍能调用Rust代码?
9 你猜对了,C++、Scala和Rust三者并列!
10 我*认为*这可行,但仍需实现以进行基准测试,因此请持保留态度:要理解我的意思,请查看组借用文章(https://verdagon.dev/blog/group-borrowing)中的“预览”示例。我相当确信它比对应的Rust程序更快,因为Rust程序需要两次额外的`.get_mut(id)`查找(加上一些额外的错误处理分支)。此外,组借用应能向LLVM表达`noalias`/`alias.scope`信息,从而不会丢失这些优化。更多详情参见此文档(https://docs.google.com/document/d/1OxnwYe704m3TtUhNnR5lQzyOxEoZKUV-omMA5sGJHmQ/edit?usp=sharing)。
11 零成本编译器优化的领域特定语言,有人感兴趣吗?
12 ……因为我篇幅用尽了。但现在我们在旁注中!其他特性包括:更好的异步故事、更佳的枚举、更优的`mustprogress`(https://llvm.org/docs/LangRef.html)优化、闭包实现trait、通用函数调用语法、良好的编译时间,以及*也许*若我们足够幸运,能恢复部分完美可重放性(https://verdagon.dev/blog/perfect-replayability-prototyped)。
---
### 目标
这曾因诸多原因看似不可能,详见《穿越不可能的FFI边界,与我的逐渐疯狂》(https://verdagon.dev/blog/exploring-seamless-rust-interop-part-2)。我忘了那篇文章包含此图:

希望我们不必如此!(*……伏笔渐浓……*13 (https://verdagon.dev/blog/golden-spike-reviving-vale-valen#note13))
总之,**更具体、明确的目标**是让此程序运行:14 (https://verdagon.dev/blog/golden-spike-reviving-vale-valen#note14)
```
import rust.nobiliav.NobiliaWindow;
import rust.nobiliav.FrameInput;
import rust.nobiliav.MainLoopCallback;
...
// Import constants exported
func main() int {
w = NobiliaWindow.new(1200, 900);
... // set up the terrain and entities
w.main_loop(
// Make a Valen closure that implements the Rust `trait MainLoopCallback`
&MainLoopCallback((win, input) => {
key = input.key();
if (key == key_arrow_left()) {
win.rotate_camera(-4, 0);
} else if (key == key_arrow_right()) {
win.rotate_camera(4, 0);
} else if (key == key_arrow_up()) {
win.rotate_camera(0, 4);
} else if (key == key_arrow_down()) {
win.rotate_camera(0, -4);
}
}));
return 0;
}
```
当然,这需要回答*许多*非常困难的问题。我将在代码中提出:
```
// How do we know what things are importable from Rust?
import rust.nobiliav.NobiliaWindow;
import rust.nobiliav.FrameInput;
import rust.nobiliav.MainLoopCallback;
...
exported func main() int {
// How do we know what static methods exist in a Rust type?
// How does Valen know what parameters rustc expects here?
w = NobiliaWindow.new(1200, 900);
...
w.main_loop(
// How do we make a Valen closure implement a Rust trait?
// How do we know the methods on a Rust trait?
&MainLoopCallback(
// How will Rust call BACK into Valen code? Monomorphizer integration?
// Can the optimizer inline a Valen function into a Rust function and vice versa?
(win, input) => {
key = input.key();
if (key == key_arrow_left()) {
win.rotate_camera(-4, 0);
} else if (key == key_arrow_right()) {
win.rotate_camera(4, 0);
} else if (key == key_arrow_up()) {
win.rotate_camera(0, 4);
} else if (key == key_arrow_down()) {
win.rotate_camera(0, -4);
}
}
));
return 0;
}
```
然而,这些问题隐藏着底下真正核心的问题。
13 当时,我认为需要重新实现Rust的泛型和trait系统。这将很不幸,尤其是Valen*已经拥有*泛型和trait系统(从Vale编译器演化而来)。后来我意识到可以做个聪明操作:当Valen代码想要调用Rust函数时,Valen编译器*不应该*查看Rust签名。相反,它应该首先(懒惰地)生成*在Valen AST中对应的函数签名*,而Valen已能理解该签名。这可能听起来不太明白,欢迎访问Valen discord(https://discord.gg/SNB8yGH),我很乐意解释更多。
14 好吧,我*必须*快速极客一下这部分。看到`MainLoopCallback((win, input) => {...})`部分了吗?那是我们*内联创建*Rust trait `MainLoopCallback`的*新子类*。这称为开放接口构造函数(https://vale.dev/guide/interfaces#open-interface-constructors),在Vale编译器中也存在。它是最复杂的特性之一,因为它处于接口、泛型和闭包的交汇点——一场完美风暴。当我为此次尝试将Vale编译器迁移到Rust时(这本身就值得一篇博客文章),我必须优先处理哪些特性恢复,而我*绝对*不想处理开放接口构造函数。结果我还是处理了!
---
### 核心问题
如许多人所知,其他语言调用Rust的最常见方式是让Rust库将函数暴露为`extern "C"`,类型暴露为`repr(C)`。这是因为C是底层语言间的事实通用翻译器。同时也因为Rust尚无稳定ABI。(https://www.reddit.com/r/rust/comments/ss2p6c/what_does_it_mean_when_people_say_that_rust_does/)15 (https://verdagon.dev/blog/golden-spike-reviving-vale-valen#note15)
“什么是ABI?”你可能会问。简化来说,“ABI”基本上是Rust回答这些问题的方式:
- “若用户调用函数时按值传递结构体,我们编译为按引用传递,还是放入寄存器?”
- “若用户指定一个`bool`、一个`u64`整数再一个`bool`,我们是否将两个`bool`相邻放置以节省空间?”16 (https://verdagon.dev/blog/golden-spike-reviving-vale-valen#note16)
唉,Rust尚未承诺采用特定ABI。因此,其他语言调用Rust的唯一选项是使用C ABI(通过`extern "C"`和`repr(C)`)。
**但C没有泛型。** C中不存在`void do_something(T* thing)`这种东西。这意味着其他语言无法调用泛型Rust函数。
**这对我们是个问题,**因为……回想上面那个`main_loop`调用:
```
exported func main() int {
...
w.main_loop(&MainLoopCallback((win, input) => { ... }));
...
}
```
以下是Rust中`main_loop`的真实面目:
```
impl NobiliaWindow {
pub fn main_loop(&mut self, cb: &mut C) {
...
}
...
}
```
没错,那个参数是*泛型*的,接受任何实现`trait MainLoopCallback`的类型。但C ABI没有泛型!而我们需要泛型。该死!我们卡住了。
因此**核心问题,**核心要解决的谜题是:*如何实现跨语言泛型?*
15 “ABI”代表“应用程序二进制接口”。
---
### 跨语言泛型
这个问题其实有两个层面:
1. 我们如何调用Rust泛型函数?
2. 我们如何调用一个*回调Valen*的Rust泛型函数?17 (https://verdagon.dev/blog/golden-spike-reviving-vale-valen#note17)
以下是一个更简单的示例来说明第一层面:
```
import rust.std.vec.Vec;
exported func main() int {
my_vec = Vec();
my_vec.push(21);
my_vec.push(42);
// Returns 42
return my_vec.pop().unwrap();
}
```
当我们说`my_vec.push(21)`时,我们是在调用泛型函数`Vec::push`。幸运的是,在《疯狂》(https://verdagon.dev/blog/exploring-seamless-rust-interop-part-2)文章中,我设法让它在C中工作了:
```
// Import a rust type directly (no bindings!)
#pragma rsuse VecInt = std::vec::Vec
// Must specify each method you want to use
#pragma rsfn VecInt_with_capacity = VecInt::with_capacity
#pragma rsfn VecInt_capacity = VecInt::capacity
#pragma rsfn VecInt_drop = VecInt::drop
// Plus the magic incantation, and...
#include "rust_deps/rust_deps.h"
#include <stdio.h>
int main() {
// ...presto, we can use rust libraries!
VecInt argv = VecInt_with_capacity(42);
printf("Capacity: %lu\n", VecInt_capacity(&argv));
VecInt_drop(argv);
return 0;
}
```
我称其为“最诡异的探索”,因为它必须进行如下特技操作:
- 运行`rustdoc`(是的,那个文档生成器!)并用`rustdoc_types`库解析其JSON输出,以确定哪些类型可用,哪些`impl`有哪些方法。
- 用一个“侦察程序”运行`rustc`,该程序只打印大小:`println!("{}", size_of::<T>())`打印24字节。(感谢matklad和literallyvoid帮我改进这部分!)
- 然后用一个“实例化程序”再次运行`rustc`,生成一个C程序可静态链接的“包装C库”。
- Vale的内存安全方法与Rust不完全匹配,因此用户必须遵循某些模式。
它有一些限制:
- 我们需要完全指定泛型参数,如`VecInt = std::vec::Vec`。
- 我们需要在`#pragma rsfn`中导入*每个*使用的方法。
但最棘手的限制是我们无法让C类型实现Rust trait。这意味着你不能使用`HashMap::get`,因为它的键必须实现`Hash`和`Eq` trait。
尽管如此,尽管诡异,2024年的解决方案实际上是个相当好的起点。上文提到跨语言泛型问题有两个层面:
1. 我们如何调用Rust泛型函数?
2. 我们如何调用一个回调Valen的Rust泛型函数?
2024年的解决方案在精神上解决了大部分第1层面,尽管它获取Rust信息的方式……
相似文章
Révo编程语言
Revo 是一种编程语言,具有清晰的数据流、错误即值、编译时执行和基于纤程的并发特性。它使用Zig构建,并提供可选类型和强推断功能。
Gossamer:一种具有真实goroutines和无暂停内存的Rust风格语言
Gossamer是一种受Rust启发的新编程语言,具有真实goroutines、基于引用计数和区域的无暂停确定性内存管理,以及配备LLVM编译的字节码虚拟机。它旨在提供富有表现力的语法,无需借用检查器或垃圾回收暂停。
V语言评测 (2023)
本文是对2023年V语言的全面评测,批评了其文档质量、内存管理缺陷以及功能不完整等问题。
编程语言中的一些好点子
本文讨论了三种编程语言特性:用于在Crystal和TypeScript等静态语言中实现动态感觉的流类型、用于确保Rust内存安全的借用检查,以及用于D语言中不变性检查的契约编程。
持久递归世界实现自主软件演化
介绍了 EvoX Genesis,一个围绕持久项目而非持久代理组织长周期软件开发的框架,以低成本和高性能自主构建了基于 Rust 的 C 编译器,并重新实现了 MESA 数值模块。