Zero:Vercel 两天 1100+ Star 的 Agent 编程语言到底解决了什么问题?

200 阅读14分钟

Zero:Vercel 两天 1100+ Star 的 Agent 编程语言到底解决了什么问题?

github.com/vercel-labs…

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")
}

这里有几个关键设计:

  1. World 不是全局变量。main 函数显式接收一个 World 能力对象,程序能做什么完全取决于运行时传了什么进来。没有隐式的 stdout 全局句柄。

  2. raises 关键字标记这个函数可能失败。Zero 的错误处理不是 try/catch 异常,而是显式的 check 模式——调用可能失败的操作时,编译器强制你处理错误。这里的 check 等价于"我知道可能出错,让调用者处理"。

  3. 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 有两条编译器路径:

  1. native/zero-c/:用纯 C 写的参考实现,确保语言能跑在任何有 C 编译器的平台上
  2. 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")
    }
}

这个程序有几个值得注意的点:

  1. fetch 函数声明了 raises { Net, Parse }——调用者一眼就知道这个操作需要网络和解析能力
  2. defer 确保 client 被关闭——不管函数是正常返回还是 raise 错误
  3. CRC32 校验——确保数据的完整性
  4. 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 成为代码的主要消费者和生产者时,编程语言应该如何设计?

它的回答是:

  1. 显式能力模型:Agent 能做什么,由编译期决定
  2. 结构化编译器输出:Agent 不执行代码就能理解代码
  3. 编译期安全沙箱:不允许编译期代码访问文件系统或网络
  4. 无 GC 的高性能运行时:适合 Agent 基础设施层

如果你对 Agent 安全感兴趣、喜欢语言设计、或者在做 Agent 基础设施——Zero 绝对值得关注。即使你不用它写生产代码,它的设计理念也会让你重新思考"Agent 和编程语言的关系"。

如果你只是想做 CRUD 业务逻辑——现阶段还是用 Python/TypeScript 吧,Zero 离"开箱即用"还有距离。


推荐指数:⭐⭐⭐⭐(4/5)

适合人群:Agent 基础设施开发者、编程语言爱好者、安全工程师 不适合:业务开发者、需要成熟生态的团队

项目地址: github.com/vercel-labs…
语言参考: zerolang.ai
当前版本: v0.1.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…

  2. Miller, M.S. (2006). "Robust Composition: Towards a Unified Approach to Access Control and Concurrency Control". PhD thesis, Johns Hopkins University. — Object-capability 模型的形式化理论。

  3. 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…

  4. Pierce, B.C. (2002). Types and Programming Languages. MIT Press. — 类型系统圣经,理解 Zero 的静态类型和代数数据类型设计需要它。www.cis.upenn.edu/~bcpierce/t…