核心特性
Zero 最与众不同的特性不是某种语法,而是编译器与读取其输出的人或程序沟通的方式。
用 zero check --json 检查一个有错误的程序,你会得到一份智能体可以直接读取的信息:
{
"ok": false,
"diagnostics": [
{
"code": "NAM003",
"message": "unknown identifier",
"line": 3,
"repair": { "id": "declare-missing-symbol" }
}
]
}
这段数据不大,但信息量很足。我们来逐个拆解每个字段及其背后的设计选择。
诊断的结构
一条诊断是一个结构化对象,描述源代码中的一个问题。标准字段包括:
code:稳定的标识符(例如NAM003)。无论编译器是哪个版本,它的含义始终不变。message:人类可读的描述。措辞可能随版本变化;含义由code确定,而不是由消息确定。line(以及其他位置字段):问题出在哪里。repair:可选的结构化元数据,描述编译器认为能解决这条诊断的修复方式。这个字段的结构本身也有文档说明,并且是稳定的。
顶层结构包含一个表示整次运行结果的布尔值 ok,以及一个 diagnostics 数组;即使运行成功,数组中也可能有警告或提示。
稳定的错误码
code 的约定是最可能让你意外的部分。NAM003 今天表示“unknown identifier”,下个月、明年也还是表示“unknown identifier”。智能体(和人类)都可以依赖这一点。
这一点很重要,因为语言模型和工具往往会缓存或记住它们见过的内容。如果 NAM003 的含义随每次发布而变化,每一次缓存的查询结果都不再可靠。把错误码固定下来,可以保证:
- 智能体的训练数据在不同版本间仍然有效。
- 文档可以按错误码索引。
- 工具流水线保持稳定。
人类可读的 message 可以随团队改进措辞而自由变化。code 才是承担关键作用的标识符。
修复元数据
当 repair 字段存在时,它会告诉使用者编译器认为哪类修复会奏效:
{
"code": "NAM003",
"message": "unknown identifier",
"line": 3,
"repair": { "id": "declare-missing-symbol" }
}
这里的 declare-missing-symbol 是修复的类型,也就是高层意图。要获取实际的编辑内容,调用 zero fix --plan --json。它会返回一个计划,包含文件路径、要修改的字节范围和新文本:
{
"diagnostic": { "code": "NAM003", "line": 3 },
"plan": {
"id": "declare-missing-symbol",
"edits": [
{ "kind": "insert", "line": 1, "text": "fun answer() -> i32 { return 42 }\n" }
]
}
}
(在你的工具链版本中,确切的字段名和结构可能有所不同;原则是“结构化数据,而不是自然语言”。)
读取这个计划的智能体有几种选择:
- 原样应用这些编辑。
- 修改后再应用。
- 拒绝这个计划,改用其他修复方式。
无论哪种情况,智能体操作的都是结构化数据,而不是试图理解一条英文建议。这正是修复计划与普通编译器中“你是不是想写……?”提示的区别。
智能体实际如何使用它
一个智能体可能运行的简化版完整流程:
- 生成或修改一个 Zero 文件。
- 对它运行
zero check --json。 - 如果结果是
{ "ok": true, ... },就继续下一步工作。 - 否则,对每一条诊断:
- 查询
code,弄清楚哪里出了问题(用zero explain或本地对照表)。 - 如果提供了
repair并且看起来安全,就调用zero fix --plan --json获取编辑内容。 - 应用(或模拟应用)这些编辑。
- 查询
- 回到第 2 步。
对比一下基于文字消息的做法:智能体要解析英文,提取可能的行号,猜测修复类型,再推断出确切要插入或替换的文本。每一步都很模糊。JSON 方式则把每一步都换成了对有文档说明的结构的查询。
不只是错误:依赖图和大小
--json 不只用于诊断。其他命令也以同样的方式提供结构化数据:
zero graph --json:以结构化数据输出一个包的依赖图。它有助于理解什么依赖什么,也方便智能体在修改调用处之前先推理清楚。zero size --json:报告编译产物在磁盘上的大小,并按目标细分。数据和人类在zero size中看到的一样,只是可以被解析。
这些结构都是设计的一部分:只要编译器有有用的数据,就会以 JSON 形式提供,工具无需抓取屏幕文本就能使用。
数据结构是什么样的
字段名和确切结构在 Zero 仓库中有文档说明,在项目达到 1.0 之前还会继续演进。你可以预期这些类别:
- 运行元数据:
ok、version、耗时。 - 诊断:
code、message、位置(文件、行、列、字节范围)、严重程度(错误、警告、提示)、可选的repair。 - 修复计划:获取时返回的结构化编辑列表。
如果你要基于这些接口开发工具,稳妥的做法是:读取你认识的字段,优雅地忽略未知字段,并以 code 作为行为决策的依据。
一个完整的快速演练
假设你的源代码用到了一个未声明的标识符:
pub fun main(world: World) -> Void raises {
check world.out.write(answer()) // 'answer' is not defined anywhere
}
运行 zero check --json 会得到类似这样的结果:
{
"ok": false,
"diagnostics": [
{
"code": "NAM003",
"message": "unknown identifier 'answer'",
"line": 2,
"column": 27,
"repair": { "id": "declare-missing-symbol" }
}
]
}
zero fix --plan --json 会返回编辑内容:
{
"diagnostic": { "code": "NAM003", "line": 2 },
"plan": {
"id": "declare-missing-symbol",
"edits": [
{ "kind": "insert", "line": 1, "text": "fun answer() -> i32 { return 42 }\n" }
]
}
}
zero fix(不带 --plan)会直接就地应用这个编辑,之后 zero check 就会返回 ok: true。每一步都是一个独立的、可检查的操作。
为什么这不只对智能体有意义
同样的特性,结构化输出、稳定的错误码和修复计划,也让这些场景更轻松:
- 编辑器和 IDE。 波浪线提示和快速修复灯泡可以直接根据
repairID 行动,而不是解析文本。 - CI 流水线。 失败记录会带上
code,方便用 grep 搜索和做成仪表盘。 - 代码批量修改工具。 批量修复可以针对某个错误码,而不是对消息做正则匹配。
这套系统是为智能体设计的,但面向人类的工具也能获得同样的好处。
下一步:智能体优先的设计
诊断系统是 Zero 智能体优先理念最具体的体现。下一篇文档 智能体优先的设计 会把视角拉远,回到背后的原则:小巧的语言表面、确定性的工具、显式的副作用,以及每一条原则为什么值得存在。
常见问题
Zero 中的 JSON 诊断是什么?
当你运行 zero check --json(或其他带 --json 的命令)时,编译器会以结构化 JSON 输出检查结果,而不是面向人类排版的文字。每条诊断都带有一个稳定的错误码(如 NAM003)、源代码位置、一条人类可读的消息,并在适用时附带一个描述如何修复的结构化 repair 字段。
Zero 为什么输出 JSON 而不是纯文本?
智能体需要解析编译器的输出。纯文本诊断是写给人看的,要提取行号或猜测修复方式,就得用正则表达式去匹配英文句子。JSON 则没有歧义:智能体读取 code 字段,查到它的文档含义,再按结构化的 repair 计划行动,完全不需要解析自然语言。
Zero 中的稳定错误码是什么?
编译器可能输出的每条诊断都有一个简短而稳定的标识符,比如 NAM003(未知标识符)。约定是:即使给人看的消息措辞变了,错误码在不同编译器版本中的含义也保持不变。智能体和工具可以直接匹配错误码,不用担心消息内容的变化。
修复计划是什么?
当编译器认为自己知道如何修复某条诊断时,会附上一个结构化的 repair 字段,说明它建议的修复类型。调用 zero fix --plan --json 会返回完整的计划:要执行的编辑操作、受影响的文件和范围。智能体可以通过程序应用、修改或拒绝这个计划。
zero explain 和 JSON 诊断有什么关系?
zero explain <code> 会返回某个诊断码的人类可读解释:这个错误是什么意思、编译器为什么报它,以及常见的修复方法。它是诊断的文字说明部分。由于错误码是稳定的,缓存下来的解释始终有效;当某个错误码不在智能体的训练数据中时,智能体就会去获取它的解释。