Zero:Vercel 两天 1100+ Star 的 Agent 编程语言到底解决了什么问题?
Vercel 上周五悄悄开源了一门新语言,名字叫 Zero。两天时间,1100+ stars。我去翻了一下源码和文档,老实说,比我想象中要扎实得多——这确实不是又一个"xx 语言重写 xx"的炒作项目。
Zero 的定位很明确:the programming language for agents。但它不是那种"用自然语言编程"的 AI 玩具,而是一门正儿八经的系统编程语言,专为构建 Agent 运行时、工具链和底层基础设施设计。
它的核心设计理念只有一句话:把副作用变成显式的、可检查的能力(capability)。
这个想法其实不新鲜——Haskell 的 IO monad、Rust 的所有权系统、OCaml 5 的代数效应,都在往这个方向走。但 Zero 走得更激进:它不仅让副作用可见,还让编译器能直接输出结构化的"程序能力图",供其他 Agent 消费。
先看一个最小的 Zero 程序:
pub fun main(world: World) -> Void raises {
check world.out.write("hello from zero\n")
}
这里有几个关键设计:
-
World不是全局变量。main函数显式接收一个World能力对象,程序能做什么完全取决于运行时传了什么进来。没有隐式的stdout全局句柄。 -
raises关键字标记这个函数可能失败。Zero 的错误处理不是try/catch异常,而是显式的check模式——调用可能失败的操作时,编译器强制你处理错误。这里的check等价于"我知道可能出错,让调用者处理"。 -
world.out.write(...)通过能力对象输出。Zero 没有print函数,没有console.log,甚至没有std.debug.print。所有 I/O 都是显式的,可审计的。
这就是 Zero 的核心哲学:Effects are capabilities, not hidden globals。
为什么 Agent 需要一门自己的语言?
说实话,我一开始也有这个疑问。用 Python 写 Agent 不行吗?用 Rust 写高性能模块不好吗?
翻了 Zero 的文档和源码之后,我觉得它解决的是三个非常具体的问题:
1. 可预测性:Agent 能知道自己能做什么
在 Python 里,任何一个 import os 都有可能执行任意系统调用。一个第三方依赖偷偷读了 ~/.ssh、发了网络请求、写了个文件——你作为调用者完全不知道。
Zero 的 World 能力模型强迫你显式声明程序的能力需求:
// 这个函数声明了它需要的能力
pub fun readConfig(world: World) -> Config raises { Io, Parse } {
let fd = check world.fs.open("config.json")
defer check world.fs.close(fd)
// ...
}
编译器在看到这段代码时,能直接告诉你这个函数需要 Io 和 Parse 两种能力。Agent 调用它之前,就能判断"这个操作需要文件系统权限,我有没有?"
2. 可审计性:编译器输出结构化的能力图
Zero 提供了 zero graph --json 命令,直接输出程序的能力依赖图:
zero graph --json examples/systems-package
输出是一个 JSON 结构,包含每个模块使用了哪些能力、暴露了哪些接口、依赖了哪些外部模块。这意味着 Agent 在不运行代码的情况下,就能知道一段代码"会做什么"。
这在安全性上的意义非常大。一个 Agent 执行用户提交的代码时,不需要在沙箱里跑一遍才知道它有没有删文件——直接看编译器输出的能力图就行了。
参考论文思路:这本质上是 Capability-based security(能力安全模型)在编程语言层面的应用。相关工作包括 Dennis & Van Horn (1966) 首次提出的 capability 模型,以及 Miller (2006) 的 object-capability 模型。
3. 可组合性:小工具拼接大系统
Zero 的另一个杀手特性是它原生支持编译到小的、自包含的二进制文件。在 zero build 时选择目标平台:
zero build --emit exe --target linux-musl-x64 examples/add.0 --out .zero/out/add
这产生的是一个静态链接的、没有运行时依赖的二进制。对于需要部署几千个微服务的 Agent 系统来说,这个特性非常实用——不需要在每个 node 上装 Python 3.11 + 一堆 pip 包。
深入 Zero 的类型系统
Zero 的类型系统设计得很有意思——它吸收了 Rust 的代数数据类型思想,但语法更简洁。
Shape:命名字段记录
shape Point {
x: i32,
y: i32,
}
fun sum(point: Point) -> i32 {
return point.x + point.y
}
let point = Point { x: 40, y: 2 }
let total = sum(point)
类似于 Rust 的 struct,但去掉了生命周期标注和 impl 块的复杂性。Field 可以有默认值:
shape Counter {
value: i32 = 0,
}
let counter = Counter {} // value 默认 0
Enum 和 Choice:代数数据类型
enum 是固定的标签集合:
enum Status {
ready,
failed,
}
但更能体现 Zero 设计哲学的是 choice——带负载的枚举:
choice Result {
ok: i32,
err: String,
}
let result: Result = Result.ok(42)
match result {
.ok => value {
if value == 42 {
check world.out.write("choice ok\n")
}
}
.err => message {
check world.out.write("choice err\n")
}
}
match 必须是穷尽的(exhaustive)。如果你只匹配了 ok 而没处理 err,编译器直接报错。这和 Rust 的 match 一样严格。
但在 Zero 里,这种设计有更深层的含义:Agent 生成的代码必须是穷尽的。一个 Agent 如果生成了不完整的 match,编译器会拒绝它——这比依赖 Agent 的 prompt 工程可靠得多。
泛型与静态特化
Zero 的泛型走的是"模板特化"路线,跟 C++ 的模板思路类似,但设计上更干净:
fun identity<T>(value: T) -> T {
return value
}
let a: i32 = identity<i32>(41)
let b: u8 = identity(7_u8)
编译器只为实际使用的类型生成具体代码。Type inference 是局部的——同一个泛型参数出现在多个位置时,所有推断出的具体类型必须一致。
更厉害的是 Zero 支持编译期静态值参数:
shape FixedVec<T, static N: usize> {
len: usize,
items: [N]T,
}
fun first<T, static N: usize>(vec: ref<FixedVec<T,N>>) -> T {
return vec.items[0]
}
// 调用时必须传入编译期常量
first<u8, 4>(&vec)
这意味着你可以在编译期就知道数组的精确大小,而不需要运行时检查。编译器为不同的 N 生成不同的代码路径,零开销。
如果你熟悉 C++ 的 std::array<T, N> 或者 Rust 的 const generics,应该能理解这个设计的价值。Zero 在这个点上做得很干净——不支持运行时注册表、无 vtables、无隐藏分配。
错误处理:显式、可组合
Zero 没有异常。它的错误处理基于三个关键字:raises、check 和 raise:
shape FixedVec<T, static N: usize> {
len: usize = 0,
items: [N]T,
fun push(self: mutref<Self>, value: T) -> Void raises { Full } {
if self.len == (N) {
raise Full
}
self.items[self.len] = value
self.len = self.len + 1
}
}
调用方用 check 处理可能失败的操作:
let mut vec: FixedVec<u8, 4> = FixedVec.init<u8, 4>([0, 0, 0, 0])
check FixedVec.push(&mut vec, 10)
check 的语义是:如果操作成功就继续,如果失败就把错误向上传播。raises { Full } 声明了这个函数会传播哪些错误类型。
这和 Rust 的 Result + ? 运算符很像,但 Zero 用 raises 关键字让函数签名直接显示可能失败的方式——调用方不需要在类型签名里看到 Result<...> 的嵌套。
Go 语言的开发者应该也会觉得亲切——check 在语义上接近 Go 的 if err != nil { return err },但 Zero 用编译器保证你不会忘记检查。
内存模型:显式引用,无 GC
Zero 不依赖垃圾回收。它使用显式引用类型来管理内存:
// 只读引用
fun readValues(data: ref<[4]i32>) -> i32 {
return data[0]
}
// 可变引用
fun updateValues(data: mutref<[4]i32>) -> Void {
data[0] = 99
}
Span<T> 和 MutSpan<T> 用于切片操作:
let bytes: [4]u8 = [65, 66, 67, 68]
let tail: Span<u8> = bytes[1..4] // 只读视图
let all: Span<u8> = bytes[..] // 完整视图
文件等需要显式生命周期的资源用 owned<T> 包装:
// File 是 owned 类型,不能隐式复制
pub fun openConfig(world: World) -> owned<File> raises { Io } {
return check world.fs.open("config.json")
}
defer 语句提供 RAII 风格的资源清理:
pub fun readConfig(world: World) -> Config raises { Io, Parse } {
let fd = check world.fs.open("config.json")
defer check world.fs.close(fd) // 函数退出时执行
// 处理 fd...
}
这个设计吸收了 Rust 的所有权和 RAII 思想,但语法上比 Rust 简单。没有生命周期标注,没有 Box/Rc/Arc 的选择焦虑——至少在当前的 v0.1.1 版本中是这样。
编译器架构:用 C 实现的编译器 + Zero 自举的编译器
Zero 的仓库结构很有意思:
native/zero-c/ ← C 实现的参考编译器
compiler-zero/ ← Zero 自举的编译器源码
examples/ ← 示例代码
docs-site/ ← 文档网站
conformance/ ← 语言和 CLI 合规测试
这意味着 Zero 有两条编译器路径:
native/zero-c/:用纯 C 写的参考实现,确保语言能跑在任何有 C 编译器的平台上compiler-zero/:用 Zero 写的编译器,是目前重点开发的自举路径
这种"先有 C 版本、再自举"的策略和 Go 语言的演进路径很像——Go 最初也是用 C 写的,后来才用 Go 重写。Zig 也是用 C++ 写的 bootstrap,然后用 Zig 自举。
从工程角度看,这个架构非常务实:
# 构建 C 编译器
make -C native/zero-c
# 使用 Zero 编译器检查程序
bin/zero check examples/hello.0
bin/zero run examples/add.0
zero run 会自动编译+运行,很适合 Agent 的 edit-test 循环。编译产出的 JSON 结构——diagnostics JSON、graph JSON、size JSON——都设计成机器可读的,Agent 可以直接解析。
编译期安全沙箱
Zero 的编译期求值器(compile-time evaluator)设计得极其保守:
- 有限的步数:防止无限循环
- 有限的递归深度:防止栈溢出
- 禁止文件系统和网络:只支持纯运算
- 禁止环境变量和进程级操作
这些限制的动机很明确:你不能在编译期偷偷读 ~/.aws/credentials 然后发网络请求。Agent 执行的代码如果是 Zero 写的,编译期就不可能泄漏任何信息。
这又回到了 Zero 的核心理念:在同一个模型中,编译期和运行期都受能力系统的约束。
对比一下传统语言的编译期求值:C++ 的 constexpr 可以执行任意代码(C++20 的 std::is_constant_evaluated() 甚至能在编译期检测自身),Rust 的 const fn 也有很多约束。Zero 在这个维度上走了最保守的路线——也更适合 Agent 场景的安全需求。
标准库现状
Zero v0.1.1 的标准库已经有了一些实用的模块:
std.mem:内存操作原语std.codec:编解码(varint、CRC32)std.parse:解析器原语std.time:时间处理std.crypto:加密原语(v0.1.1 新增)std.http:HTTP 客户端(v0.1.1 新增)std.net:网络操作(v0.1.1 新增)
一个使用标准库的例子:
use std.codec
pub fun main(world: World) -> Void raises {
let len = std.codec.encodedVarintLen(300)
let checksum = std.codec.crc32("zero")
if len == 2 && checksum > 0 {
check world.out.write("codec primitives ok\n")
}
}
use 导入模块后,用 std.codec.encodedVarintLen 这样的完全限定路径访问函数——没有隐式导入,没有命名空间污染。
实战:用 Zero 写一个简单的 HTTP Agent 工具
虽然 Zero 还很年轻,但它的设计已经允许写出有实际价值的程序。下面是一个简单的 HTTP GET 请求工具:
use std.http
use std.codec
shape FetchResult {
status: u16,
body: Span<u8>,
checksum: u32,
}
pub fun fetch(world: World, url: Span<u8>) -> FetchResult raises { Net, Parse } {
let client = check std.http.newClient(world)
defer check std.http.closeClient(&client)
let response = check std.http.get(client, url)
let checksum = std.codec.crc32(response.body)
return FetchResult {
status: response.status,
body: response.body,
checksum: checksum,
}
}
pub fun main(world: World) -> Void raises {
let result = check fetch(world, "https://api.example.com/data")
if result.status == 200 {
check world.out.write("fetch ok, checksum verified\n")
} else {
check world.out.write("fetch returned non-200\n")
}
}
这个程序有几个值得注意的点:
fetch函数声明了raises { Net, Parse }——调用者一眼就知道这个操作需要网络和解析能力defer确保 client 被关闭——不管函数是正常返回还是raise错误- CRC32 校验——确保数据的完整性
Span<u8>作为字符串类型——不需要隐式复制,零开销引用
踩坑记录
我在尝试了解 Zero 的过程中发现了一些需要注意的问题:
1. 语言不成熟,API 不稳定
Zero 目前还是 v0.1.1,CHANGELOG 里明确写了:
"Zero is experimental and still changing."
这意味着今天写的代码明天可能不兼容。v0.1.1 的改动就包括"重写了公开文档结构"和"删除了尚未准备好的模块文档"——这些都是 breaking changes。
2. 泛型支持很有限
目前 Zero 的泛型只支持"静态特化"——每个类型参数都必须有调用点。没有 trait/interface/typeclass 的概念,你不能写 fun sort<T: Ord>(items: &mut [T]) 这样的代码。这在写通用算法时很不方便。
3. 标准库覆盖不足
虽然有 std.http、std.crypto、std.net,但很多常见任务目前都不支持:JSON 解析、正则表达式、进程管理、数据库驱动……你大概率需要自己写或等官方支持。
4. 自举编译器仍在早期
compiler-zero/ 目录还在活跃开发中,目前官方的编译路径主要还是走 C 编译器(native/zero-c/)。如果你期待"Zero 编译器完全由 Zero 编写"的体验,还得再等等。
5. 缺少包管理和生态
Zero 目前没有类似 npm/cargo/pip 的包管理器,也没有第三方包的注册中心。这意味着你写的代码复用起来比较困难——现阶段更适合作为底层工具嵌入到其他系统,而不是作为独立应用开发的语言。
个人感受
翻了 Zero 的代码和文档两天,我有几点真实感受:
好的方面:
这门语言的设计品味真的很高。World 能力模型、raises 显式错误、defer 资源清理、编译期安全沙箱——这些特性组合在一起,体现出一种"为 Agent 安全而生"的完整设计理念。不是那种"我们也加个 async/await 吧"的跟风设计。
Vercel 的工程规范也很扎实。仓库结构清晰、有完整的 CLI 合规测试、有结构化 JSON 输出、连 zero skills 命令都考虑到了——让外部 Agent 能通过 CLI 获取语言指导。
更难得的是,Zero 在保持安全性的同时没有牺牲性能。C 编译器→静态二进制→无 GC 的路径,保证了它能胜任 Agent 底层基础设施的性能要求。
不那么好的方面:
这语言目前还太早期了。如果你现在想用 Zero 做生产级的东西,大概率会被各种缺失特性卡住。标准库不完整、泛型不成熟、没有包管理——这些都是"能用但不够好用"的问题。
但话说回来,两天 1100+ stars 的速度说明社区对这个方向有真实的需求。Agent 的安全问题越来越严重——上周就有好几个 Agent 越狱的案例——一门从设计层面就解决安全问题的编程语言,价值不言而喻。
总结
Zero 不是一个"更好的 Python"或"更快的 JavaScript"。它试图解决的问题更根本:当 AI Agent 成为代码的主要消费者和生产者时,编程语言应该如何设计?
它的回答是:
- 显式能力模型:Agent 能做什么,由编译期决定
- 结构化编译器输出:Agent 不执行代码就能理解代码
- 编译期安全沙箱:不允许编译期代码访问文件系统或网络
- 无 GC 的高性能运行时:适合 Agent 基础设施层
如果你对 Agent 安全感兴趣、喜欢语言设计、或者在做 Agent 基础设施——Zero 绝对值得关注。即使你不用它写生产代码,它的设计理念也会让你重新思考"Agent 和编程语言的关系"。
如果你只是想做 CRUD 业务逻辑——现阶段还是用 Python/TypeScript 吧,Zero 离"开箱即用"还有距离。
推荐指数:⭐⭐⭐⭐(4/5)
适合人群:Agent 基础设施开发者、编程语言爱好者、安全工程师 不适合:业务开发者、需要成熟生态的团队
项目地址: github.com/vercel-labs…
语言参考: zerolang.ai
当前版本: v0.1.1
参考文献
-
Dennis, J.B. and Van Horn, E.C. (1966). "Programming Semantics for Multiprogrammed Computations". Communications of the ACM, 9(3), 143-155. — Capability-based security 模型的奠基性论文。dl.acm.org/doi/10.1145…
-
Miller, M.S. (2006). "Robust Composition: Towards a Unified Approach to Access Control and Concurrency Control". PhD thesis, Johns Hopkins University. — Object-capability 模型的形式化理论。
-
Cardelli, L. and Wegner, P. (1985). "On Understanding Types, Data Abstraction, and Polymorphism". ACM Computing Surveys, 17(4), 471-522. — 类型系统理论基础,Zero 的类型设计可以从中找到理论渊源。dl.acm.org/doi/10.1145…
-
Pierce, B.C. (2002). Types and Programming Languages. MIT Press. — 类型系统圣经,理解 Zero 的静态类型和代数数据类型设计需要它。www.cis.upenn.edu/~bcpierce/t…