Jam Programming Language

Jam Programming Language

Jam Programming Language 13 May • 2026

Before I get into any of this: I’m not bashing any language. I have real respect for all of them. Every language has good things the others don’t, and there’s no such thing as a perfect language. As the creator of Rio Terminal, I’ve never said a negative word about another terminal, and I won’t say one here about another language either. Anyone spending their time trying to build something they believe in has my admiration. What follows is a take on tradeoffs, not on the people behind the work.

在进入正题之前,我先声明:我并非在抨击任何编程语言。我对所有语言都怀有真正的敬意。每种语言都有其独特之处,且不存在所谓的“完美语言”。作为 Rio Terminal 的创建者,我从未对其他终端发表过负面评价,在这里我也不会对其他语言进行贬低。任何投入时间去构建自己信仰之物的人都值得我钦佩。以下内容仅是对权衡取舍的探讨,而非针对开发这些工具的人。

One more thing: Jam is still on its way to v1.0. The mechanics below are real and running in the compiler today, but specifics may change before the language stabilizes, so treat what’s here as the current state and not the final form. One more thing though: if you think learning a programming language in the age of LLMs is a waste of time, then this post isn’t for you. Saying it up front so you can save some of yours. Ok, all being said. Lemme start.

还有一点:Jam 语言目前仍在迈向 1.0 版本的过程中。下文提到的机制是真实存在且已在编译器中运行的,但在语言稳定之前,具体细节可能会有所变动,因此请将本文视为当前状态而非最终形态。另外,如果你认为在 LLM(大语言模型)时代学习编程语言是浪费时间,那么这篇文章不适合你。提前说明这一点,是为了节省你的时间。好了,言尽于此,让我们开始吧。

– I love Rust. I really do. I’ve run Rust workshops. I’ve been the person who pioneered Rust adoption at a couple of the companies I’ve worked at. I have spent a lot of energy trying to bring teams along. The problem is that Rust keeps getting more complicated. Don’t take that the wrong way. Rust has a clear philosophy and is making the choices that philosophy demands. But in real-life work, on real teams, you want to ship things fast and you want the team committed to the language. The cliff between “I can write some Rust” and “I am productive in Rust” is steep enough that good engineers stall on it, and you spend months pulling them up.

我热爱 Rust,真的。我举办过 Rust 工作坊,也曾在任职的几家公司里率先推动 Rust 的采用。我投入了大量精力去引导团队。问题在于,Rust 变得越来越复杂了。别误会,Rust 有着清晰的哲学,并正在做出该哲学所要求的选择。但在现实工作和真实的团队中,你需要快速交付产品,并希望团队能全身心投入到该语言中。“我会写一点 Rust”与“我能高效使用 Rust”之间的鸿沟非常巨大,以至于优秀的工程师也会在此停滞不前,你需要花费数月时间来帮助他们跨越。

This is why Zig has been striving lately. Zig keeps programmers close to C-like languages: small surface, immediate mental model, no syntactic noise. The joy of C without most of the C foot-cannons. The catch is that Zig isn’t a safe language. Uninitialized reads, manual cleanup, nothing at the language level stopping a use-after-free. Zig leaves all of that in your hands and trusts you to be careful.

这就是为什么 Zig 最近发展势头强劲。Zig 让程序员保持在类 C 语言的舒适区:表面积小、心智模型直观、没有语法噪音。它保留了 C 语言的乐趣,同时去掉了大部分“自毁式”的陷阱。但问题在于,Zig 不是一门安全的语言。未初始化的读取、手动清理,语言层面没有任何机制来阻止“释放后使用”(use-after-free)错误。Zig 将这一切留给你自己处理,并信任你会足够小心。

You can argue that’s fine. A single experienced programmer working solo can hold the invariants in their head and is unlikely to ship a use-after-free. The problem is that real software is almost never that. Real software has dependencies, and a single CVE in any of them lands on you. Real teams mix experience levels, and the less senior end of the team makes mistakes more often. Look at big Zig or C++ projects in production: they lean heavily on Valgrind, AddressSanitizer, and fuzzing, running the same checks over the same flow over and over, sometimes inside dependencies the team doesn’t own. That’s the cost of unsafe-by-default. The verification work doesn’t disappear; it gets pushed out of the language and into tools, CI, and postmortems. Software ends up less reliable in production and harder to maintain over time. Jarred Sumner, creator of Bun, on rewriting Bun from Zig to Rust. Exactly the tax described above.

你可能会说这没问题。一个经验丰富的程序员独自工作时,可以将不变性(invariants)记在脑子里,不太可能写出“释放后使用”的 bug。但问题是,现实中的软件开发几乎从不如此。真实的软件有依赖项,其中任何一个出现 CVE(漏洞),你都难辞其咎。真实的团队成员经验水平参差不齐,资历较浅的成员更容易犯错。看看生产环境中的大型 Zig 或 C++ 项目:它们严重依赖 Valgrind、AddressSanitizer 和模糊测试,一遍又一遍地对同一流程运行相同的检查,有时甚至是在团队无法掌控的依赖项内部进行。这就是“默认不安全”的代价。验证工作并没有消失,它只是从语言本身被推到了工具、CI 和事后分析中。最终导致软件在生产环境中可靠性降低,且随着时间推移更难维护。Bun 的创建者 Jarred Sumner 在将 Bun 从 Zig 重写为 Rust 时,所描述的正是这种代价。

In the age of AI, safety has become a must, or at least highly desirable. A lot of code in production today is written, or at least drafted, by something that isn’t a human. I am not making a value judgement; it is just where we are. The shape of the bottleneck has shifted: ten years ago most folks would wrote code, now most of them review code. Ten years ago the compiler caught half the bugs and the human caught the other half; now the compiler has to catch all of them, because the human going line by line with full intent is gone or distracted. With code volume rising and review surface flat, the language has to be the one keeping things honest.

在 AI 时代,安全性已成为必需品,或者至少是高度理想的特性。如今生产环境中的许多代码都是由非人类编写的,或者至少是起草的。我不是在做价值判断,这只是现状。瓶颈的形态已经改变:十年前大多数人是在写代码,现在大多数人是在审查代码。十年前,编译器能发现一半的 bug,人类发现另一半;现在,编译器必须发现所有的 bug,因为那种逐行审阅、全神贯注的人类已经消失或分心了。随着代码量的增加和审查能力的停滞,语言本身必须成为确保代码质量的守门人。

Safety, lower learning curve and high performance were the biggest reasons I started Jam programming language. The question I’ve been working on: how do you keep the joyful, immediate feel of a C-like language (Go, Zig, modern C) while making the language safe without a garbage collector? How do you give people the C ergonomic without the C bug class? The compromise that fell out is a language that draws from four places. Today I’m focusing on two; the other two will get their own posts.

安全性、较低的学习曲线和高性能是我启动 Jam 编程语言的主要原因。我一直在研究的问题是:如何在没有垃圾回收器的情况下,既保持类 C 语言(如 Go、Zig、现代 C)那种令人愉悦、直观的体验,又能确保语言的安全性?如何在提供 C 语言人体工程学的同时,规避 C 语言的 bug 类型?最终得出的折中方案是一门汲取了四个方面特性的语言。今天我重点介绍其中两个,另外两个将在后续文章中讨论。

Mutable value semantics as described in Racordon, Abrahams et al. 2022.1 Bindings own their values, parameter borrows live only for the duration of a single function call, and no reference or lifetime syntax appears anywhere in user code. This is what replaces the borrow checker.

正如 Racordon、Abrahams 等人在 2022 年所描述的“可变值语义”(Mutable value semantics)。绑定(Bindings)拥有它们的值,参数借用仅在单次函数调用期间有效,且用户代码中不会出现任何引用或生命周期语法。这就是替代借用检查器(borrow checker)的机制。

Rust’s drop system. Types declare a drop function, the compiler synthesizes the call at every scope exit, and a small dataflow analysis catches use-of-uninitialized at compile time. The result is a language where: Bindings own their values and resources clean themselves up, so every binding of a drop-bearing type fires its drop function automatically when the binding goes out of scope.

Rust 的 drop 系统。类型声明一个 drop 函数,编译器在每个作用域退出时合成调用,并通过简单的数据流分析在编译时捕获“未初始化使用”错误。其结果是:绑定拥有其值,资源会自动清理,因此每个带有 drop 特性的绑定在超出作用域时,都会自动触发其 drop 函数。

Jam drop system In Jam you write: Jam 的 drop 系统,在 Jam 中你可以这样写:

const File = struct {
    fd: i32,
    fn drop(self: mut File) {
        close(self.fd);
    }
};

export fn useFile() i32 {
    const f: File = { fd: 7 };
    return f.fd;
}

There is no explicit cleanup. No defer, no manual call, no syntax marking the end of f’s lifetime. The compiler synthesizes the drop call. Here is the LLVM IR Jam emits for useFile:

这里没有显式的清理操作。没有 defer,没有手动调用,也没有标记 f 生命周期结束的语法。编译器会自动合成 drop 调用。以下是 Jam 为 useFile 生成的 LLVM IR:

define i32 @useFile() #0 {
  %1 = alloca %File, align 4
  store %File { i32 7 }, ptr %1, align 4
  %2 = getelementptr inbounds nuw %File, ptr %1, i32 0, i32 0
  %3 = load i32, ptr %2, align 4
  call void @__drop_File(ptr %1)
  ret i32 %3
}

The call void @__drop_File(ptr %1) one line before the ret is the entire story (%1 here is the alloca for the source-level binding f). The compiler tracked that f is a binding of a drop-bearing type, walked the function’s drop scope on exit, and emitted the call automatically. The mangled name (__drop_File rather than drop) is what lets multiple types each have their own drop fn without colliding at the LLVM level. The pointer-passed self is the mode-aware ABI doing its job: the drop function takes self: mut File, which lowers as a ptr parameter, so the call site passes the binding’s address directly and d

在 ret 之前的那一行 call void @__drop_File(ptr %1) 就是全部的关键(这里的 %1 是源代码级绑定 f 的 alloca)。编译器追踪到 f 是一个带有 drop 特性的绑定,在函数退出时遍历了 drop 作用域,并自动生成了该调用。修饰后的名称(__drop_File 而非 drop)使得多个类型可以各自拥有 drop 函数,而不会在 LLVM 层面产生冲突。通过指针传递的 self 是模式感知 ABI(mode-aware ABI)在发挥作用:drop 函数接收 self: mut File,它被降级为 ptr 参数,因此调用点直接传递了绑定的地址,并且 d…