Skip to content

【Zig 日报】Zig 的可爱语法 #309

@jiacai2050

Description

@jiacai2050

Zig's Lovely Syntax 这篇文章探讨了编程语言 Zig 的语法特点,并将其与 Rust、C 等其他语言进行了比较,指出 Zig 在语法设计上的优点和一些独特之处。作者认为,尽管语法是语言最不有趣的细节,但 Zig 在许多方面做到了“恰到好处”。

文章的主要观点和细节总结如下:

  1. 整数字面量 (Integer Literals):

    • Zig 不使用后缀来指定整数类型(如 92u8),而是将所有整数字面量视为 comptime_int 类型。
    • 这些字面量在编译时已知,并在赋值或类型转换时隐式强制转换为特定类型(例如 const x: i32 = 92;)。
    • 这并非类型推断,而是隐式编译时强制转换,通常意味着 var x = 92; 需要显式类型。
  2. 字符串字面量 (String Literals):

    • Zig 提供了独特的原始多行字符串语法,以 \ 开头,每行以 \ 引导。
    • 这种语法避免了转义 \ 的问题,能很好地处理缩进,并且每个行都是一个单独的词法标记,使得换行符始终是空白符。
    • 作者认为这是 Zig 相对于 Rust 的一个巨大改进,Rust 的 r##""## 语法存在缩进、嵌套和未闭合字面量等问题。
  3. 记录字面量 (Record Literals):

    • Zig 采用类似 C 的记录字面量语法,例如 const p: Point = .{ .x = 1, .y = 2 };
    • .{ 看起来有点奇怪,但 .x = 1 与赋值语法 obj.x = 1 一致,这使得通过搜索 .x = 来查找字段写入操作非常有用,有助于代码理解。
  4. 前缀类型 (Prefix Types):

    • 与 C 语言复杂的类型声明规则不同,Zig 的所有类型都是前缀形式,例如 u32[3]u32?[3]u32*const ?[3]u32
    • 指针类型是前缀,但指针解引用是后缀 (ptr.* = 92;),阅读起来更自然。
  5. 标识符 (Identifiers):

    • Zig 支持“原始”标识符语法 @ "a name with a space",用于避免关键字冲突或导出非标准名称的符号。它重用了 Zig 的内置函数 (@TypeOf) 和字符串语法。
  6. 函数 (Functions):

    • Zig 像 Rust 一样使用 fn foo 语法声明函数,将 fn 关键字和函数名放在一起,便于搜索。
    • Zig 函数声明省略了 Rust 的 -> 箭头,例如 fn add(x: i32, i32) i32。作者认为这减少了视觉噪音,因为 Zig 没有带有推断返回类型的 Lambda 表达式,返回类型总是强制性的。
    • 作者更喜欢 void 而不是 () 作为类型名称。
  7. 局部变量 (Locals):

    • Zig 使用 constvar 绑定值,与 Rust 的 const(在 Zig 中相当于 comptime)略有不同。
    • 作者认为 constvar 更长,不够简洁,Kotlin 的 valvarfun 更优。
    • Zig 像 Rust 一样使用 'name' (':' Type)? 的类型声明语法,优于 Type 'name',因为可选后缀更容易解析。
  8. 连接即控制流 (Conjunction Is Control Flow):

    • Zig 不使用 &&||,而是使用 andor 关键字。
    • 作者认为这更容易阅读和输入,更深层的原因是这些布尔运算符是短路的,属于控制流,使用关键字强调了这一点,避免了误解。位操作仍使用 &|
  9. 显式返回 (Explicit return):

    • Zig 像 Rust 一样有语句和表达式,但更偏向语句,要求显式 return
    • 没有 Lambda 使得 return 的作用域始终清晰。
    • 块表达式的值是 void,不以表达式结尾,这消除了 Rust 中关于分号的“心智负担”。
    • 如果需要块返回一个值,Zig 支持通过标签块 break :blk value 来实现。
  10. If 语句 (If):

    • Rust 强制 if 语句使用大括号,以避免“悬空 else”问题,但这使得单行 if 显得笨重。
    • Zig 采用传统做法,要求括号,大括号可选,允许三元运算符风格的 if (a) b else c
    • Zig 编译器的一部分是强制性的、不可配置的格式化器,可以捕获可能掩盖错误的格式化问题(例如 1 -2),这弥补了语法上的潜在风险。
  11. 循环 (Loops):

    • Zig 像 Python 一样允许循环带有 else 子句。
    • 循环是表达式,使得命令式搜索非常简洁,例如用于编译时查找类型。
    • Zig 没有像 Rust 或 Go 那样专门的无限循环语法,而是通过 while (true)for (0..safety_bound) 来表达,这与 Zig 的编译时语义紧密相关。
    • forwhileifswitchcatch 都使用 Ruby/Rust 风格的捕获值命名语法 |element|
  12. 名称的清晰度 (Clarity of Names):

    • Zig 禁止变量遮蔽 (shadowing)。
    • 没有“Prelude”,所有标准库内容都需要显式 @import("std")
    • 没有全局导入,需要显式导入特定项,例如 const ArrayList = std.ArrayList;
    • Zig 没有继承、混入、ADL、扩展函数、隐式 trait 等,因此 x.foo() 保证 foox 类型上声明的方法。
    • 曾允许方法和字段同名,但后来被移除。
    • Zig 没有命名空间,同一作用域中只能有一种 foo,这极大地简化了名称解析,作者惊叹于这种方法带来的便利。
  13. 一切皆表达式 (Everything Is an Expression):

    • 这是 Zig 语法最显著的(通过其缺失)特点,与 Zig 最深刻的语义密切相关。值、类型和模式都使用相同的表面语法表示,然后在语义分析阶段进行分类。
    • 这与许多语言不同,后者通常为值、类型和模式提供不同的语法家族。
    • 这种统一性减少了语言的“繁忙感”,例如在类型位置使用 if 表达式是可能的。
  14. 泛型 (Generics):

    • 泛型类型实例看起来像函数调用:ArrayList(u32)
    • Zig 泛型参数从不推断,如果函数接受 3 个编译时参数和 2 个运行时参数,则总是以 5 个参数语法调用。
    • 通过编译时闭包 (comptime closures) 解决频繁指定类型的问题,一旦类型在变量中被捕获,后续使用就不需要再次指定。作者认为这使得 Zig 中的类型注释负担很低。
  15. 声明字面量和结果位置语义 (Declaration Literals and Result Location Semantics):

    • Zig 没有 Hindley-Milner 类型推断,但依赖于“结果位置语义”进行类型传播。
    • if 表达式的两个分支产生不同 comptime_int 值时,需要在赋值时显式指定类型或向下推导强制转换。
    • Zig 的编译器更像一个解释器,它在评估表达式时向下传递结果位置和类型。
    • 这解释了枚举的简洁 .variant 语法和记录字面量的开头点 .{}@ResultType().whatever 的简写)。
    • .{} 语法一开始可能让人感到奇怪,但提供了强大的 API,例如免费获得了命名参数和默认参数。
  16. 内置函数 (Built-ins):

    • Zig 使用 @ 前缀表示内置函数,例如 @divExact@bitCast@as@import
    • 这提供了一个独立的语法命名空间来处理编译器支持的原始操作,避免了操作符重载等问题。
    • @as(i32, 92) 中的类型在前,是结果类型语义的应用。
    • @import("./foo.zig") 是作者最喜欢的内置函数,它清晰地指明了文件来源,并且是一个“反向语法糖”,尽管看起来像函数调用,但参数必须是语法上的字符串字面量。

总结性思考:
作者认为 Zig 的这些“愚蠢的语法决定”加起来使得语言读起来非常愉快。他强调,语言功能越少,所需的语法就越少,而更少的语法通常是好事。语言功能之间并非正交,会相互影响。即使功能集固定,选择一个好的具体语法(解析无歧义、易于搜索、易读易写)仍然需要大量工作。虽然可以借鉴其他语言的成熟方案,但也应勇于创新。

唯一不喜欢的地方:
作者在撰写文章时发现了一个不喜欢的语法形式:带有增量操作的 while 循环 while (i < 10) : (i+=1) { ... }。他认为这类似于 C 语言的 for 循环(缺少声明部分),控制流跳跃,并且 : 是 Zig 中唯一一个用符号而非关键字表达控制流的例子,感觉不一致。由于现在有了 for(0..10) |i| 形式,他认为这种 while 循环变得多余。

加入我们

Zig 中文社区是一个开放的组织,我们致力于推广 Zig 在中文群体中的使用,有多种方式可以参与进来:

  1. 供稿,分享自己使用 Zig 的心得
  2. 改进 ZigCC 组织下的开源项目
  3. 加入微信群Telegram 群组

Metadata

Metadata

Assignees

No one assigned

    Labels

    日报daily report

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions