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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions