学习

学习 Zero

使用仓库中的可运行示例进行实践语言导览。

程序从 main 开始

最小的示例是 examples/hello.0

pub fn main Void world World !  check world.out.write "hello from zero\n"

pub 导出入口点。fn 声明一个函数。main 接收一个 World 能力,而不是使用隐藏的全局变量。

Void 是返回类型,! 表示该函数可能失败。

运行:

zero check examples/hello.0

效果使用能力

在 Zero 中,输出不是魔法。程序通过 world.out 进行写入:

check world.out.write "hello from zero\n"

write 可能失败,因此使用 check 调用它。使用 check 的函数需要声明 !

let 绑定值

examples/hello-let.0 引入了一个局部绑定:

pub fn main Void world World !  let message "hello from a binding\n"  check world.out.write message

当一个值不应改变时,使用 let。只有当值需要被有意重新赋值时,才使用 mut

编写函数

examples/add.0 定义了一个辅助函数并在 main 中调用它:

fn answer i32  ret + 40 2 pub fn main Void world World !  let value answer()  if == value 42    check world.out.write "math works\n"  else    check world.out.write "math broke\n"

函数签名以返回类型开头,然后是参数名/类型对。当你想要带值离开函数时,使用 ret

当前原生编译器支持显式整数宽度:

i8 i16 i32 i64
u8 u16 u32 u64
usize isize

整数字面量支持十进制、0x 十六进制、0b 二进制、0o 八进制、_ 分隔符,以及 _u8_usize 等后缀。

字面量会根据其上下文进行检查。let byte u8 255 可以工作。let byte u8 256 会在 zero check 时失败。

已有的整数值保持其确切类型。当你有意在原始整数类型之间进行转换时,使用 as

let count u32 0x12c_u32let byte u8 count as u8

当前的类型转换支持仅限于整数到整数的转换。

f32f64 可用于十进制浮点字面量。无类型浮点字面量默认为 f64

let ratio f64 1.0e-3let small f32 0.5let total + ratio 2.0

浮点数不会与整数隐式混合,也不会在不同宽度之间隐式混合。

char 也可用作一种独特的字节大小原始类型,用于单引号字节字面量。它不能与整数互相转换:

let letter char 'A'let newline char '\n'let same == letter '\x41'

f16、Unicode 标量字面量以及非整数值的类型转换不属于当前的公开接口。

使用控制流

Zero 拥有普通的 if / else 块:

if == value 42  check world.out.write "math works\n"else  check world.out.write "math broke\n"

当前原生子集还支持 while 循环:

while keepGoing  check world.out.write "loop\n"

当你需要整数计数器时,使用范围 for

for index in 0..4  if == index 2    continue  check world.out.write "tick\n"

使用 break 离开最近的循环,使用 continue 跳到下一次迭代。

条件必须是 Bool,因此要显式比较值,而不是依赖真值整数。

优先使用直接条件和显式状态。检查器会拒绝向不可变绑定赋值,因此只有当循环或算法确实需要修改状态时才引入 mut

type 建模数据

使用 type 定义命名记录。examples/point.0 定义了一个点并将其传递给辅助函数:

type Point  x i32  y i32 fn sum i32 point Point  ret + point.x point.y pub fn main Void world World !  let point Point . x 40 y 2  let total sum point  if == total 42    check world.out.write "point works\n"

类型字面量需要命名其字段。字段访问使用 value.field

使用字段默认值

类型可以为调用者可能省略的字段提供默认值:

type Counter  value i32 0 let counter Counter .

默认值会降级为普通的具体初始化器。如果字段没有默认值,类型字面量必须显式初始化它。

enumchoice 表示备选方案

使用 enum 表示一组固定的名称:

enum Status  ready  failed

当备选方案可能携带载荷时,使用 choice

choice Result  ok i32  err String

examples/result-choice.0 构造了一个带载荷的 choice 并对其进行匹配:

let result Result Result.ok 42match result  ok value    if == value 42      check world.out.write "choice ok\n"  err message    check world.out.write "choice err\n"

匹配必须是穷尽的。如果一个 choice 有 okerr,则必须处理两者。将载荷名称放在分支名称之后,以便在该分支内绑定它。

导入标准库模块

使用 use 导入标准库模块。examples/codec-varint.0 使用了 std.codec

use std.codec pub fn main Void world World !  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"

examples/parse-cursor.0 使用了 std.parse

use std.parse pub fn main Void world World !  let digit std.parse.isAsciiDigit "7"  let ident std.parse.isIdentifierStart "_"  if && digit ident    check world.out.write "parse primitives ok\n"

当前原生编译器支持来自 std.memstd.codecstd.parse 以及专注于持续时间的 std.time 的早期辅助工具。

编解码辅助工具现在返回其文档中记录的宽度,例如 std.codec.readU16(...) -> u16

面向 CLI 的辅助工具也可用:

pub fn main Void world World !  let first std.args.get 1  if first.has    let written std.fs.write ".zero/out/name.txt" first.value    if > written 0      check world.out.write "wrote argument\n"

std.args.get 返回 Maybe<String>,因为请求的参数可能不存在。

当前的 std.fs 辅助工具是托管 API。当你需要显式的 FsFileowned<File> 资源示例时,请参考标准库文档。

组织包

包有一个 zero.json 清单文件和 src/ 目录下的源文件。

{
  "package": { "name": "systems-package", "version": "0.1.0" },
  "targets": { "cli": { "kind": "exe", "main": "src/main.0" } }
}

examples/systems-package/src/main.0 导入模块和本地声明:

use std.codecuse std.parseuse std.time pub fn main Void world World !  defer cleanup()  let current Status status()  let result Result Result.ok  let word std.codec.readU32 "abcd"  let digits std.parse.scanDigits "123abc"  let duration std.time.add (std.time.ms 5) (std.time.seconds 1)  if && (&& (== digits 3) (> word 0)) (> (std.time.asMsFloor duration) 0)    check world.out.write "systems package\n"

检查包:

zero check examples/systems-package

运行测试

Zero 测试块与源代码放在一起:

test "addition is stable"  expect (== (+ 40 2) 42)

运行测试:

zero test conformance/native/pass/test-blocks.0
zero test --json --filter addition conformance/native/pass/test-blocks.0

失败的测试会包含失败测试名称并以非零退出码退出。

检查跨平台目标

目标名称是显式的。使用 zero targets 查看支持情况,然后将 --target 传递给 checkbuildgraphsize

zero targets
zero check --target linux-musl-x64 examples/memory-package
zero build --target linux-musl-x64 examples/memory-package --out .zero/out/memory-package

检查器会拒绝不可用的能力,例如在目标中立构建上的托管 std.fs

使用诊断

诊断功能已足够稳定,可供人类和代理使用:

zero check --json conformance/check/fail/unknown-name.0
zero explain NAM003
zero fix --plan --json conformance/check/fail/unknown-name.0

每条 JSON 诊断包含代码、范围、期望/实际字段、帮助、修复安全性和修复元数据。

理解 defer 清理

defer cleanup() 将清理安排在当前作用域结束时执行:

pub fn main Void world World !  defer cleanup()  check world.out.write "work\n"

使用 defer 处理在作用域退出时应执行的清理工作,包括通过 retbreakcontinue 退出的情况。

T 定义了规范的非抛出 fn drop Void self mutref<Self> 时,存活的 owned<T> 局部变量也会在作用域结束时被清理。直接的用户调用如 value.drop() 仍然被拒绝,以保持清理的确定性。

阅读面向内存的类型

一些示例引入了底层 Zero 代码使用的术语:

type BufferView  bytes Span<u8> pub fn main Void world World !  let bytes Span<u8> std.mem.span "zero"  let view BufferView . bytes bytes  if && (== (std.mem.len view.bytes) 4) (== view.bytes[0] 122)    check world.out.write "span ok\n"

常用术语:

  • Span<T> 是对连续值的只读视图。
  • MutSpan<T> 是对可变固定数组存储的显式可写视图。
  • 当前可运行的布局包括 Span<T>MutSpan<T> 和单元素 [N]T
  • 索引支持 span、固定数组和面向字节的 String 值。
  • 切片是半开区间:start..endstart....end..
  • 索引和切片会生成边界陷阱。
  • 索引左值可用于可变类型字段、固定数组和 MutSpan<T>
  • 无分配辅助工具包括 std.mem.span、泛型 std.mem.lenstd.mem.eqlBytes
  • Maybe<T> 表示可能不存在的值。
  • ref<T>mutref<T> 使引用可变性显式化。
  • Alloc 是分配器能力。当前的分配辅助工具保持显式,仅限于文档中记录的分配器支持 API。

你不需要所有这些来编写 hello world,但你会在系统代码和 C 互操作中看到它们。

跨越 C 边界

使用 extern cextern type 进行 C 互操作声明:

extern c "config.h" as config extern type CConfig  enabled bool  limit i32

互操作类型应使布局和 ABI 边界清晰。对于必须匹配 C 布局的数据,使用 extern type

接下来阅读什么

  • 示例索引按学习顺序列出了示例。
  • 语言参考文档记录了语法和语义。
  • 原生编译器指南解释了源码构建和编译器验证。
  • 诊断说明了如何阅读和使用错误。
  • 实现状态说明了当前的限制。