202411

观点/教程

Why am I writing a JavaScript toolchain in Zig?

JAM 作者写的一篇文章,分析里市面上现有的 JS 工具链(bundler、formatter、linter 等),虽然已经很好用,但是不够快。下面是他举的几个例子:

  • Lossless, cache efficient syntax trees,现在通用的 JS 语法树表示是 ESTree,尽管设计上很简洁,但在遍历时不够高效,需要有遍历多次 才能得到有用信息(eslint 里就有四次!),而且都是指针的树结构非常不利用重复利用 CPU,Carbon 编译器就有一种更紧凑的 AST 表示。
  • Compile time AST query processing。Lint 的规则大部分都是模式匹配,大部分时候都有多个嵌套的 if 逻辑,为了简化插件开发者,eslint 采用了一种 esquery 的语法,示例:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    if (
    node.type == "CallExpression" &&
    node.callee.type === "MemberExpression" &&
    node.callee.object.type === "Identifier" &&
    node.callee.object.name === "child_process"
    ) {
    // many of these checks are to satisfy typescript^
    }
    
    
    if (matches(
      'CallExpression[callee.object.name = child_process]',
       node
    )) {
    // much better
    }

    esquery 的问题在于执行效率,通过借助于 zig 的 comptime 来在编译器翻译 esquery 就避免了运行时开销。

Advent of Code in Zig | Loris Cro's Blog

一年一度的 AoC 又到了,这篇文章里给出了一些使用的技巧来帮助大家用 Zig 来解决 AoC 问题。

  • 工具链,最新的版本 0.13 和 zls
  • 手册,和 How to read the standard library source code · ziglang/zig Wiki
  • 使用 embedFile 来嵌入输入的测试用例:

    1
    2
    3
    4
    5
    6
    7
    8
    
    const std = @import("std");
    const input = @embedFile("path/to/input.txt");
    
    pub fn main() !void {
     for (input) |byte| {
        //...
     }
    }
  • 分词

  • 数据操作

    • 解析数字, std.fmt.parseInt()
    • 位操作,可以用任意宽度的数字(u1,u2 等), std.BitStack, std.DynamicBitSet 这两个也非常有用

更重要的,作者最后提到 AoC 可能不是学习 Zig 的最好方式:

虽然 AoC 非常有趣,但它并不是练习软件工程的方法。 每个 AoC 练习都要求你找到一个问题的解决方案,虽然你需要编写一个程序来解决这个问题, 但你的程序将是一个只需运行一次(一次正确)的一次性脚本。

当你的软件需要稳健、优化和可维护时,Zig 就会大显身手,而这些对于 AoC 来说都不重要。

因此,请注意,虽然肯定能用 Zig 解决 AoC 问题,而且 Zig 的某些功能甚至能帮助您取得比其他语言更快的进展,但它最终还是针对软件工程进行了优化,而这并不是您在 AoC 中要做的事情。

Zig Reproduced Without Binaries

一个很有趣的实验,在 0.10 版本中,Zig 编译器实现了自举,即可以用老版本的 Zig 来编译 Zig 源码,生成最新的 Zig 二进制。这里重新复习一下这个复杂的流程:

之所以复杂,问题在于老版本的 Zig 从哪里来呢?对于 Zig 来说就是 zig1.wasm,它是用没自举前的 Zig,利用 LLVM 后端,以 wasm32-wasi 为目标生成的二进制文件。 为了保证足够小,这里面只保留了 C 后端,这样就得到了一个小到可以放到代码仓库中的 Zig 编译器。这篇文章就是证明这个文件没有被私自篡改过!

  • 之后利用 Zig 团队自己写的 wasm2c.c 把 zig1.wasm 编译成 zig1.c,之后用 cc 编译 zig1.c 就可以得到 stage1 的 zig 编译器
  • 之后再用 zig1 编译 zig 源码,由于 zig1 之后 C 后端,因此这里得到的产物是 zig2.c,再利用 cc 就可以可以 stage2 的 zig。 zig2 功能上已经完备,但是速度很慢(没有经过 LLVM 优化)
  • 最后再用 zig2 继续编译 zig 源码,得到最后的 zig3,这也是我们下载 zig 安装包时包含的版本
  • 如果再继续用 zig3 来编译 zig 源码,得到的 zig4 会和 zig3 一模一样。

细节可以参考:

项目/工具

Builds (Zig) - GoReleaser
版本发布工具 GoReleaser 支持了 Zig
FOLLGAD/zig-ai: OpenAI SDK with streaming support
A bunch of links to blog posts, articles, videos, etc for learning Zig
Super-ZIG/cli: Easy command line interface in ZIG.
deckarep/zigualizer
Zigualizer: A music visualizer built with Zig, powered by the FFT algorithm.
freref/fancy-cat
PDF reader for terminal emulators using the Kitty image protocol
Dr-Nekoma/lyceum
An MMO game written in Erlang (+ PostgreSQL) + Zig (+ Raylib)

202410 | 向 Zig 软件基金会认捐 30 万美元

重大事件

向 Zig 软件基金会认捐 30 万美元

Mitchell 在其最新的博客上宣布:我和我的妻子向 Zig 软件基金会 (ZSF) 捐赠了 300,000 美元。

两年内每年分期支付15万美元。第一期已经转账。

我从 2019 年的某个时候开始关注 Zig 项目。 我在 2021 年公开分享了我对该项目的兴奋之情。 同年晚些时候,我开始使用 Zig,到 2022 年初,我开始撰写关于 Zig 的文章,并为编译器做出贡献。 2023 年,我公开分享了用 Zig 编写的终端项目 Ghostty。

如今,我大部分的编码时间都花在了 Zig 上。 我的家人喜欢支持我们相信的事业2。 作为其中的一部分,我们希望支持那些我们认为可以带来变革和影响的独立软件项目,这既是回馈给我如此之多的社区的一种方式,更重要的是,这也是彰显和鼓励为热爱而构建的文化的一种方式。 Zig 就是这样一个项目。

观点/教程

Zig is everything I want C to be

对 Zig 的特色进行了简单扼要的介绍,主要有:

  1. UB 行为检测。

    • Zig 的指针不能是 null,需要用 optional 类型
    • C 里面的 void* 等价于 Zig 里面的 ?*anyopaquevoid 在 C 里面有两个意思,第一是『什么都没有』,第二是『类型不确定』,但 void 在 Zig 中只有第一个含义,因此用了 anyopaque 来表示类型擦除的指针(type-erased pointers)。
    • 数组越界检查
    • 整数溢出
  2. Bitfield, packed struct 可以方便的用来进行协议解析,比如对于 32 位的 RISC-V 的指令,可以这么定义解析:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    const IType = packed struct {
     opcode: u7,
     rd: u5,
     funct3: u3,
     rs1: u5,
     imm: i12, // For sign-extension
    };
    
    const encoded_instr: u32 = 0xFFF34293;
    const instr: IType = @bitCast(encoded_instr);
  3. comptime,Zig 进行元编程的基础,类型是一等成员
  4. 与 C 无缝交互, zig cc 是交叉编译的首选

Critical Social Infrastructure for Zig Communities | Loris Cro's Blog

对于一个试图共同学习如何制作大家都喜欢的软件的社区来说,能够分享想法并开展合作至关重要,但社交平台的不断起伏会导致连接中断,这对于一个从一开始就希望去中心化的社区来说是个大问题。

我有这种想法已经有一段时间了,但随着时间的推移,我们似乎越来越清楚地认识到,我们需要投资于能够长期保持可靠的交流形式,在这种交流形式中,变化是一种信号,表明社区正在发生转变(因此需要一种新的网络形态),而不是表明所选择的社交平台即将被收购/上市/加入人工智能大战。

开发者日志:迈向可靠社会基础设施的第一步

The Zig Website Has Been Re-engineered

Zig 官网已经用 Zine 重写!

项目/工具

laohanlinux/boltdb-zig
a zig implement kv database
zigler
Zig NIFs in Elixir
gdonald/blackjack-zig
Console Blackjack written in Zig
rabinnh/zig-vscode-linux
Instructions on setting up VSCode to debug Zig on Linux
lframosferreira/brainzuck
Brainf*ck interpreter written in Zig 0.12.0! Have fun!
BitlyTwiser/snek
A simple CLI parser to build CLI applications in Zig
zml/zml
High performance AI inference stack. Built for production.
BitlyTwiser/zdotenv
A port of Godotenv for Zig
sbancuz/OpenMP-zig
An implementation of the OpenMP directives for Zig
tusharsadhwani/zigimports
Automatically remove unused imports and globals from Zig files.
Mario-SO/zigitor
Video editor 🎬 written in Zig ⚡ using raylib
pwbh/ymlz
Small and convenient yaml parser for Zig

202407 | Zig 成为最热门的编程语言

重大事件

这篇文章里,作者引用 Stackoverflow 2024 年的调查报告,指出 Zig 语言是最热门的编程语言之一,并且 Zig 开发者的薪水都很高,平均年收入为75,332美元!

Zig 受欢迎程度

Zig 受欢迎程度

Zig 薪水对比

Zig 薪水对比

尽管使用 Zig 语言的开发者仅占调查人数的 1%,但上升趋势明显。Zig 语言的倡导者、自由和开放源码软件开发者 Ali Cheragi 说:

Zig 的魅力在于它的简洁性、现代设计以及在底层控制和运行时安全性之间取得的平衡。

Zig 开发者的一些观点:

  • 我选择 Zig 作为我的日常用语,是因为它独特的功能和目标组合。我被 Zig 的安全性所吸引,因为它可以让我控制最底层的部件。
  • 与许多其他语言不同,Zig 可以与现有的 C 代码实现真正的无缝互操作。出于多种原因,这一点至关重要。
  • Zig 正在对大量编程基础架构进行彻底改造,而这些基础架构在过去 40 年里无人敢碰。 C 和 C++ 是著名的核心编程语言,在这两种语言中,你可以完全控制硬件。 但与此同时,这些语言的工具链却非常糟糕。 Zig 允许用户涉猎这些核心编程语言,但可以使用更好的工具链,兼容各种语言和更丰富的功能。

观点/教程

Improving Your Zig Language Server Experience

Loris Cro 的最新文章,介绍了一个改进 Zig 编码体验的小技巧,十分推荐大家使用。具体来说是这样的: 通过配置 zls,达到保存文件时,自动进行源码检查,而且速度非常快!

1
2
3
4
{
 "enable_build_on_save": true,
 "build_on_save_step": "check"
}

将上述内存保存到 zls 的配置文件中,(路径可以通过 zls --show-config-path 查看 ),zls 就会在保存时,自动执行 zig build check ,这个 check 一般来说是这样的:

1
2
3
4
5
6
7
8
9
const exe_check = b.addExecutable(.{
   .name = "foo",
   .root_source_file = b.path("src/main.zig"),
   .target = target,
   .optimize = optimize,
});

const check = b.step("check", "Check if foo compiles");
check.dependOn(&exe_check.step);

由于 Zig 目前的一个 bug(#18877),这个 exe_check 不能作为 install、run 的依赖,否则在编译时,就不会增加 -fno-emit-bin 选项。 而这个选项的作用就是让 Zig 来分析我们的代码,但是不会调用 LLVM 来生成最终的二进制文件,因此速度会比较快。

这个配置有个缺点,就是它是个全局配置,在 zigtools/zls#1687 有讨论如何改成项目级别的,本质上就是定制 zls 的启动参数。

1
zls --config-path zls.json

这样不同的项目就可以用不同的检查步骤了。

Systems Distributed '24

作者对这次会议的一个回顾总结,议题主要有如下几个方向:

  • Systems Thinking and Engineering Culture
  • The Rise of New Software Abstractions
  • Ensuring Safe and Correct Software
  • Lessons from Building Distributed Databases
  • Notes from Water Cooler Chats

C Macro Reflection in Zig – Zig Has Better C Interop Than C Itself

该作者分享了利用 typeInfo 来在编译时获取字段名的能力,要知道,在 C 里面是没有这个功能的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
pub export fn WindowProc(hwnd: win32.HWND, uMsg: c_uint, wParam: win32.WPARAM, lParam: win32.LPARAM) callconv(windows.WINAPI) win32.LRESULT {
    // Handle each type of window message we care about
    _ = switch (uMsg) {
        win32.WM_CLOSE => win32.DestroyWindow(hwnd),
        win32.WM_DESTROY => win32.PostQuitMessage(0),
        else => {
            stdout.print("Unknown window message: 0x{x:0>4}\n", .{uMsg}) catch undefined;
        },
    };
    return win32.DefWindowProcA(hwnd, uMsg, wParam, lParam);
}

上面这个函数是 Window 编写窗口应用时用到的回调函数,Window 操作系统会把用户触发的事件通过 uMsg 传递过来,为了能够从一个数字,找对对应的名字,在 Zig 里面可以用如下函数实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// The WM_* macros have values less than 65536, so an array of that size can
// represent all of them
fn get_window_messages() [65536][:0]const u8 {
    var result: [65536][:0]const u8 = undefined;
    @setEvalBranchQuota(1000000);
    // Loop over all struct fields and match against the expected prefix
    for (@typeInfo(win32).Struct.decls) |field| {
        if (field.name.len >= 3 and std.mem.eql(u8, field.name[0..3], "WM_")) {
            const value = @field(win32, field.name);
            result[value] = field.name;
        }
    }
    // We return by value here, not by reference, so this is safe to do
    return result;
}

A TypeScripter's Take on Zig (Advent of Code 2023)

以下该作者的一些心得体会:

  • Zig 没有 scanf 等价物,正则表达式也不方便。因此,对于解析输入,它是拆分、拆分、拆分。最后,我分解出了一些 splitIntoBuf 和提取 IntsIntoBuf 帮助程序,这些帮助程序可以很快地读取大多数问题的输入。
  • Zig 支持所有大小的 int,一直到 u65536。如果出现溢出,请尝试使用更大的整数类型。我在一些问题上使用了 u128和 i128。
  • StringToEnum 是解析受限制的字符串或字符集的一个简单技巧。
  • 可以在结构上定义一个 format 方法,使它们按照您的喜好打印。
  • 尽量避免将字符串复制到 StringHashMap 中用作键。从 JS 发出这样的命令感觉很自然,但是在 Zig 中会很尴 尬,因为您需要跟踪这些字符串以便稍后释放它们。如果您可以将您的键放入一个结构或元组中,那将会工作得 更好,因为它们具有值语义。如果需要字符串,可以使用切片。
  • 注意数值范围的错误。如果你想包含 max,它是 min..(max + 1) ,而不是 min..max
  • 代码中将有大量的@intCast。
  • 我发现奇怪的是 Zig 有一个内置的 PriorityQueue,但是没有内置的 Queue,可以用 std.SinglyLinkedList 替代
  • 用于处理字符串的许多函数都在 std.mem 中,例如 std.mem.eql 和 std.mem.startsWith
  • 使用 std.met.eql 比较 structs,而不是 =
  • 有一个按偏移量和长度切片的技巧: array [start..][0..length]
  • 记忆函数通常是很有用的。我不知道 Zig 有没有通用的方法
  • 调试构建比优化构建慢得多,有时候慢10倍。如果你在一个合理的时间内得到一个答案的10倍之内,尝试一个不同的发布模式。
  • 迭代时不要对数组列表进行修改
  • 在 JavaScript 允许您内联表达式的某些情况下,您可能需要分解出一个变量来澄清生存期。看看这个问题

项目/工具

18alantom/fex
A command-line file explorer prioritizing quick navigation.
griush/zm
SIMD Math library fully cross-platform

202406 | 0.13 来了

重大事件

2024-06-07,0.13.0 发布,历时不足 2 个月,有 73 位贡献者,一共进行了 415 次提交! 这是一个相对较短的发布周期,主要原因是工具链升级,例如升级到 LLVM 18

一个比较大的 Breaking changes 是 ComptimeStringMap 被重命名为了 StaticStringMap , 使用方式也发生了变化,更多细节可参考:#19682

1
const map = std.StaticStringMap(T).initComptime(kvs_list);

0.14.0 发布周期的主题将是编译速度。将在 0.14.0 发布周期中努力实现一些即将到来的里程碑:

  • 使 x86 后端成为调试模式的默认后端。
  • COFF 的链接器支持。消除对 LLVM LLD 的依赖。
  • 启用增量编译以实现快速重建。
  • 将并发引入语义分析,进一步提高编译速度。

观点/教程

Leveraging Zig's Allocators

老朋友 openmymind 的又一篇好文章:如何利用 Zig 的 Allocator 来实现请求级别的内存分配。 Zig Allocator 的最佳应用。这里它的中文翻译。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
const FallbackAllocator = struct {
  primary: Allocator,
  fallback: Allocator,
  fba: *std.heap.FixedBufferAllocator,

  pub fn allocator(self: *FallbackAllocator) Allocator {
    return .{
      .ptr = self,
      .vtable = &.{.alloc = alloc, .resize = resize, .free = free},
    };
  }

  fn alloc(ctx: *anyopaque, len: usize, ptr_align: u8, ra: usize) ?[*]u8 {
    const self: *FallbackAllocator = @ptrCast(@alignCast(ctx));
    return self.primary.rawAlloc(len, ptr_align, ra)
           orelse self.fallback.rawAlloc(len, ptr_align, ra);
  }

  fn resize(ctx: *anyopaque, buf: []u8, buf_align: u8, new_len: usize, ra: usize) bool {
    const self: *FallbackAllocator = @ptrCast(@alignCast(ctx));
    if (self.fba.ownsPtr(buf.ptr)) {
      if (self.primary.rawResize(buf, buf_align, new_len, ra)) {
        return true;
      }
    }
    return self.fallback.rawResize(buf, buf_align, new_len, ra);
  }

  fn free(_: *anyopaque, _: []u8, _: u8, _: usize) void {
    // we noop this since, in our specific case, we know
    // the fallback is an arena, which won't free individual items
  }
};

fn run(worker: *Worker) void {
  const allocator = worker.server.allocator;

  // this is the underlying memory for our FixedBufferAllocator
  const buf = try allocator.alloc(u8, 8192);
  defer allocator.free(buf);

  var fba = std.heap.FixedBufferAllocator.init(buf);

  while (queue.pop()) |conn| {
    defer fba.reset();

    var arena = std.heap.ArenaAllocator.init(allocator);
    defer arena.deinit();

    var fallback = FallbackAllocator{
      .fba = &fba,
      .primary = fba.allocator(),
      .fallback = arena.allocator(),
    };

    const action = worker.route(conn.req.url);
    action(fallback.allocator(), conn.req, conn.res) catch { // TODO: 500 };
    worker.write(conn.res);
  }
}

On Zig vs Rust at work and the choice we made

这篇文章作者描述了所在公司在改造老 C/C++ 项目时,为什么选择了 Zig 而不是 Rust。 重写的项目运行在多个平台上(Web、移动端、VR 设备),因此最靠谱的方案就是暴露一个 C API,然后通过 FFI 来调用。在做决策时,重点关注以下两点:

  • 新语言与 C 的交互性
  • 工程师扩展代码库的难易程度(如招聘和维护)

下面是 Zig VS Rust 的优势:

RustZig
成熟度更流行、稳定;使用范围更广
包管理Cargo 业界领先比 Makefile 好用
安全内存安全
SIMDnightly 支持通过 Vector 类型支持
C 交互性生态丰富编译器本身就是 C 编译器,这样就可以逐步重写项目

如果只是根据上面的比较,貌似还看不出选择 Zig 的动机,因此作者在最后提到:

Zig 大大减少了移植现有代码库和确保所有平台兼容性所需的时间和精力。我们的团队无法相信 Rust 能让这一切变得如此简单。

相信这也是大部分人选择 Zig 的原因:简洁、高效。

Packing some Zig before going for the countryside

作者列举的一些 Zig 学习资料、常用类库。该作者的另一篇文章也有不少资料:2024 Collection of Zig resources

Why I am not yet ready to switch to Zig from Rust

Turso CTO 的一篇文章,他本身是个资深 C 程序员,而且也比较喜欢 C,但 C 不是一门安全的语言,因此通过 Rust,作者可以避免 写出 SIGSEGVS 的代码,尽管 Rust 是门复杂的语言,但是因为它有完善的生态(有大公司如微软、谷歌等做背书)、已经内存安全等特点, 已经是作者系统编程的首选。

对于 Zig,尽管作者也表达了喜欢,但由于 Zig 的生态不完善,没有足够多的学习资料,因此作者觉得目前阶段选择 Zig 并不会带来 工作上生产力的提高。这一点说的无可厚非,试想一下,如果一个项目所有的依赖都需要自己做,工作效率确实很难提上去。

但是笔者有一点不能理解,就是该作者觉得 comptime 不好用,相比之下,他更喜欢 C 里面的宏。comptime 就是为了 C 宏的不足 而诞生的,社区普遍也觉得 comptime 是个新颖的设计,笔者也是第一次见到这个观点,只能说,萝卜青菜,各有所爱。

其他社区的一些讨论:LobstersHacker News

项目/工具

malcolmstill/zware
Zig WebAssembly Runtime Engine
Cloudef/zig-aio
io_uring like asynchronous API and coroutine powered IO tasks for zig

202405

观点/教程

Thoughts on Zig

又一篇 Zig 初学者的使用体验文档,如果你也在犹豫要不要学 Zig,这是个不错的经验参考。

I'm sold on Zig's simplicity : r/Zig

一个具有资深经验开发者,在这里描述了自己选择业余项目语言的经历:

  • Rust 越来越复杂,有种发展成 C++ 的趋势
  • C++ 新版本的特性(比如 module)LSP 支持的不够好,而且历史包袱严重
  • C 缺少元编程,并且没有命名空间

最后从 Andrew 的一个播客了解到 Zig,经过自己尝试,发现了 Zig 没有辜负他的期望,尽管是第一次写 Zig,但基本上没有什么难度, 每次遇到问题,仔细想几分钟就差不多有答案了。下面是他罗列的 Zig 的一些优势:

  • 十分简洁,import 返回的是一个 struct,和其他变量一样使用
  • 与 C 无缝交换,
  • 具有 Result 效果的错误处理
  • 唯一缺失的就是『接口』,但这一点并不是很关键,就像在 C里也没有,但是 C 可以做任何事

Zig's New CLI Progress Bar Explained

Andrew 的一篇文章,讲述了在最新版的 Zig 中,对进度条的改进实现,现在的进度展示更加友好。

实现的难点在于在多线程环境下,如何保证高性能,文章中大致讲述了其实现:

  • 首先通过预先分配好需要使用的结构,保证后续无需在进行 heap 申请
  • 通过 atomic 操作来实现一个无锁的 freelist,用于申请、释放 Node

Writing a task scheduler in Zig

Openmymind 作者的又一力作,通过编写一个任务调度器,讲述了多线程编程的基本要领:

  • 共享的数据要加锁
  • 条件变量要和锁一起使用,会有虚假唤醒的问题,因此在被唤醒时,需要重新检查状态是否正确。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    fn run(self: *Self) void {
    while (true) {
      self.mutex.lock();
      while (self.queue.peek() == null) {
        self.cond.wait(&self.mutex);
      }
      // TODO
    }
    }

    它会在 wait 前释放锁,在 wait 返回时先加锁,类似下面的实现:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    fn wait(c: *std.Thread.Condition, mutex: *std.Thread.Mutex) void {
    // do some setup
    // ...
    
    mutex.unlock();
    
    // whatever happens, we'll always return with this locked
    defer mutex.lock();
    
    // wait for signal
    // or timeout if calling timedWait
    // ...
    }

项目/工具

zigar
Enable the use of Zig code in JavaScript project。它可以让你直接在 JS 中调用 zig 代码,背后原理是编译成了 wasm 实现的。
srijan-paul/nez
An emulator for the NES console.
deckarep/ziglang-set
A generic and general purpose Set implementation for the Zig language
akarpovskii/tuile
A Text UI library for Zig

202404 | Zig 0.12.0 正式释出

重大事件

千呼万唤的 0.12.0 版本终于 2024-04-20 正式释出了!这次版本历时 8 个月,有 268 位贡献者,一共进行了 3688 次提交!社区内的一些讨论:Hacker NewsLobsters。 这是它的 Release notes。ZigCC 对这个文档进行了翻译、整理,方便大家阅读:

并且还在 2024-04-27 举行了一次线上的 meetup 来庆祝这次发布,这是会议的总结:0.12.0 Release Party 回顾

0.12.0 这个版本,对用户来说,最重大的变更就是构建系统的稳定了,这对于 Zig 生态的发展是十分关键的一步,试想一个项目用到的依赖之间版本不兼容, 这是十分痛苦的事情,毫无疑问这是阻碍 Zig 生态发生的绊脚石,没有之一。好在这一切都在 0.12 这个版本解决了,用户可以基于 Step 构成的有向无环图来编译自己的项目,不需要再折腾 CMake、Makefile、Vcpkg、Git submodule 等工具,所有的依赖使用 zon 来管理即可。 读者如果对 Zig 构建系统还不熟悉,可以参考:

期待一年后 Zig 的生态!

观点/教程

Zig 中任意精度整数用途与实现

由于 CPU 在访问内存时,一般都会有对齐的要求,对于这种非常规的数字,在内存中的地址会是怎样的呢?可以做一个简单的实验:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const std = @import("std");

const Foo = packed struct {
  a: u3,
  b: u2,
};

pub fn main() !void {
  const vs = [_]u3{ 1, 2, 3 };
  for (&vs) |*b| {
      std.debug.print("{p}-{b}\n", .{ b, b.* });
  }

  std.debug.print("U3 size: {d}\n", .{@sizeOf(u3)});
  std.debug.print("Foo size: {d}\n", .{@sizeOf(Foo)});

  const foos = [_]Foo{
      .{ .a = 1, .b = 3 },
  };

  std.debug.print("foo as bytes: {b}\n", .{std.mem.sliceAsBytes(&foos)});

  for (foos) |b| {
      std.debug.print("{any}-{any}\n", .{ &b.a, &b.b });
  }
}

输出:

1
2
3
4
5
6
7
u3@104d11a2c-1
u3@104d11a2d-10
u3@104d11a2e-11
U3 size: 1
Foo size: 1
foo as bytes: { 11001 }
u3@16b196367-u2@16b196367

通过前三个输出可以知道,每个 u3 实际占用一个字节,但当用在 packed 结构中,就会变成 3 个 bit。其中的 11001 就是字段 a b 混合后的值,且 a 是三位,b 是高两位。

Learnings From Building a DB in Zig
作者分享了在一次 3 天的 Hackthon 中,使用 Zig 开发一个数据库的经历。
build.zig.zon dependency hashes
讲解了 zon 中依赖的 hash 是怎么计算出来的
play with new comptime var rule of zig 0.12.0
To SIMD and beyond: Optimizing a simple comparison routine
作者在这里循序渐进的介绍了几种数字比较的技巧,从基本的方案,到 Vector,到最后利用 bit 的特点,来逐步优化,并用 godbolt 查看生成的汇编代码,是一篇不错的文章。
Documentation takes another step backwards : r/Zig
一个 Reddit 用户对文档的抱怨

项目/工具

rofrol/zig-companies
A list of companies using Zig in production.
akarpovskii/tuile
A Text UI library for Zig
mntnmntn/zenith
A very minimal text editor in Zig,支持 0.12.0 版本
chung-leong/zigar
Enable the use of Zig code in JavaScript project
jnordwick/zig-string
Zig string library that includes small string optimization on the stack
FalsePattern/ZigBrains
Yet another zig language plugin for intellij

202403 | ziglang.cc 正式上线

重大事件

https://ziglang.cc/

之前 ZigCC 所有项目都是托管在 GitHub 之上,网页基于 Pages 构建,域名自然也就是 github.io 的,虽然 GitHub 提供了很多利于开发者的服务,但过于依赖 GitHub 这种商业公司,还是不利于 ZigCC 的长远发展,域名是其中很重要一个,有了独立域名,网页托管选择就多了,比如 Cloudflare Pages

另一个大家比较关心的问题就是 0.12 的发版,虽然 milestone 显示还剩 10 来个 open 的 issue,但是这只是个幌子,核心团队还是有可能随时 delay。不过从剩下的 issue 来分析,主要问题还剩两大类:

  1. 构建系统完善
  2. 修复之前功能带来的回顾问题(regression 这个 tag)

新功能看来是已经 ready 了,但这并不是说剩下的这些工具就好解决了,Andrew 在 Zig with Andrew Kelley 这一期播客里提到的 90-90 理论很好的解释了这一点:

(开发软件时)前 90% 的代码要花费 90% 的开发时间,剩余的 10% 的代码要再花费 90% 的开发时间。

当然,后面 ZigCC 也会紧密关注发布动态,有消息第一时间分享给大家。耐不住寂寞的朋友,可以先去刷刷 Zig 的 discord。

观点/教程

Redesign How Autodoc Works

Andrew 在这个 PR 里重构了现有的文档系统 Autodoc,之前的实现问题很多。比如:

  • 很多功能重复的文件,最夸张的是 lib/docs/ziglexer.js ,它是用 JS 实现的 Zig 的解析器,其实 Zig 已经在标准库中暴露解析相关 API,通过 wasm 就可以调用
  • 功能更强,因为新设计方案不再处理 ZIR,而是直接处理源文件,这意味着它拥有100% 的信息,不需要向后拼凑任何东西。
  • sources.tar 文件经 HTTP 层解压后,直接进入 wasm 模块的内存。使用 std.tar 对 tar 文件进行解析,并对源文件进行就地解析,同时在哈希表中添加一些额外的计算。虽然可以通过 Worker 来加快解析速度,但单线程的解析速度已经非常快,因此这并不是非常有必要。 快来体验最新的文档系统吧:https://ziglang.org/documentation/master/std/
Zig, Rust, and other languages

老朋友 Phil Eaton 的文章,在这里他针对以下几点进行了语言对比:

  • 内存管理。Zig 最大的问题是不支持 RAII,一个近似的概念是 arenas 分配器。
  • 标准库,主要是讨论标准库是否应该精简为主, node_modules 是业界经常提到的一个反面例子,一般支持精简的人会认为,

    • 语言的 std 不容易出现 breaking changes,想 Python 里就有 urllib、urllib2、urllib3 这三个网络库, 但是社区推荐的并不是这三个,而是 requests,这样 std 的位置就有些尴尬
    • Zig 目前的标准库算是中等大小,json、compress 压缩等功能都有
  • 显示分配,这算是 Zig 的强项,其他语言很少有支持这个的,因此作者在这建议增加一种类似 must-not-allocate 的注解, 这样高级语言里,也可以保值某些操作不会有内存分配。
Why does an extraneous build step make my Zig app 10x faster?

作者在这篇文章里分享了自己遇到的一个很有意思的问题, 同一份代码,执行方式不同,竟然有不同的耗时。最小复现代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// src/main.zig

const std = @import("std");

pub fn countBytes(reader: anytype) !u32 {
  var count: u32 = 0;
  while (true) {
      _ = reader.readByte() catch |err| switch (err) {
          error.EndOfStream => {
              return count;
          },
          else => {
              return err;
          },
      };
      count += 1;
  }
}

pub fn main() !void {
  var reader = std.io.getStdIn().reader();

  var timer = try std.time.Timer.start();
  const start = timer.lap();
  const count = try countBytes(&reader);
  const end = timer.read();
  const elapsed_micros = @as(f64, @floatFromInt(end - start)) / std.time.ns_per_us;

  const output = std.io.getStdOut().writer();
  try output.print("bytes:           {}\n", .{count});
  try output.print("execution time:  {d:.3}µs\n", .{elapsed_micros});
}

两种执行方式:

1
2
3
4
5
6
7
$ echo '00010203040506070809' | xxd -r -p | zig build run -Doptimize=ReleaseFast
bytes:           10
execution time:  13.549µs

$ echo '00010203040506070809' | xxd -r -p | ./zig-out/bin/count-bytes
bytes:           10
execution time:  162.195µs

可以看到,通过 zig build run 的方式来执行时,耗时相比直接执行编译好的二进制要快 10 倍。 问题的关键在于 shell 的 pipeline 的执行机制,对于 A | B 这样一个简单的 pipeline,一般本能的会认为 B 只会在 A 执行完后才开始执行,但是实际上它们是同时运行的,因此,在上面的例子里 main 函数的执行时间在 zig build run 方式下, 其实执行的要晚一些,因为它需要先执行编译操作,因此造成了这个误差。

One Bilion rows in zig
作者用 1BRC 这个项目作为 Zig 的练手项目,里面用到了 mstange/samply 这个 Profiler 工具,还起来还比较实用。
Zig defer Patterns

Matklad 最新的一篇文章,Ziggit 讨论链接。里面讲述了 defer 除了做资源回收外,其他的一些惯用法,里面有几个有趣的点:

1
errdefer comptime unreachable

文中称这个是 Zig 的巅峰用法😅, errdefer unreachable 还比较好理解,即在执行出错时,执行 unreachable ,加上 comptime 呢?

其实这是阻止 Zig 编译器生产错误处理的代码,即在编译时期保证下面的逻辑不会出错,确实用的很巧妙!一个简单的例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
const std = @import("std");

test "errdeferWithUnreachable" {
  errdefer comptime unreachable;
  const i = try inc(1);
  try std.testing.expectEqual(i, 2);
}

fn inc(a: i8) !i8 {
  if (a > 10) {
      return error.TooLarge;
  }
  return a + 1;
}

直接执行 zig test ,在编译时会报下面的错误:

1
2
test.zig:4:23: error: reached unreachable code
    errdefer comptime unreachable;

虽然 a 是个运行时的值,但是 errdefer comptime unreachable 不关心这个,只要 Zig 编译器开始生成 ErrorSet 相关代码, 编译就会报错,去掉上面的 if 代码块后,测试就可以正常执行。一个实际的例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
   assert(std.math.isPowerOfTwo(new_cap));

   var map: Self = .{};
-  defer map.deinit(allocator);
-  map.pointer_stability.lock();
   try map.allocate(allocator, new_cap);
+  errdefer comptime unreachable;
+  map.pointer_stability.lock();
   map.initMetadatas();
   map.available = @truncate((new_cap * max_load_percentage) / 100);

@@6581,7 @@ pub fn HashMapUnmanaged(
   self.size = 0;
   self.pointer_stability = .{ .state = .unlocked };
   std.mem.swap(Self, self, &map);
+  map.deinit(allocator);

+
+test "getOrPut allocation failure" {
+    var map: std.StringHashMapUnmanaged(void) = .{};
+    try testing.expectError(error.OutOfMemory, map.getOrPut(std.testing.failing_allocator, "hello"));
+}

可以看到, 这么修改后,就可以保证 map.deinit(allocator) 语句之前没有错误可能产生!读者可以细细品味一下这个用法。

另一个小技巧是 errdefer 竟然支持错误捕获,即下面这种用法:

1
2
3
4
5
6
const port = port: {
  errdefer |err| std.log.err("failed to read the port number: {!}", .{err});
  var buf: [fmt.count("{}\n", .{maxInt(u16)})]u8 = undefined;
  const len = try process.stdout.?.readAll(&buf);
  break :port try fmt.parseInt(u16, buf[0 .. len -| 1], 10);
};
Build system tricks
介绍了 zig build 的使用技巧,这些技巧有助于在确保方便地命名和布局构建步骤的同时,如何使用构建系统的每个部分。
Using Zig with WebAssembly
如何将 Zig 编译成 wasm,并传递复杂的参数。

项目/工具

xataio/pgzx

Create PostgreSQL extensions using Zig. 一个例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const std = @import("std");
const pgzx = @import("pgzx");

comptime {
  pgzx.PG_MODULE_MAGIC();

  pgzx.PG_FUNCTION_V1("char_count_zig", char_count_zig);
}

fn char_count_zig(input_text: []const u8, target_char: []const u8) !u32 {
  if (target_char.len > 1) {
      return pgzx.elog.Error(@src(), "Target char is more than one byte", .{});
  }

  pgzx.elog.Info(@src(), "input_text: {s}\n", .{input_text});
  pgzx.elog.Info(@src(), "target_char: {s}\n", .{target_char});
  pgzx.elog.Info(@src(), "Target char len: {}\n", .{target_char.len});

  var count: u32 = 0;
  for (input_text) |char| {
      if (char == target_char[0]) {
          count += 1;
      }
  }
  return count;
}
Manage Zig installations

又又又叒一个 Zig 管理工具,Rust 开发。

1
2
3
zman default latest
zman default master
zman default 0.12.0
mahdifrmz/qooil
用 Zig 语言编写的文件传输软件
timfayz/pretty
Pretty printer for arbitrary data structures in Zig
liyu1981/zcmd.zig
Zcmd is a single file lib to replace zig's std.childProcess.run with the ability of running pipeline like bash.
zigcc/zig-milestone
Zig milstone monitor

202402 | Zig 2024 Roadmap 新鲜出炉

重大事件

Andrew 最近在 zigshow 节目中介绍了 Zig 2024 年的规划,主要有以下几点:

  1. 0.12 版本会尽快发布
  2. 编译时间现在太慢,进而导致修 bug 的时间长,因此 core team 会优先解决这个编译时间问题。在这个看板中,有相应的进度,主要是:Ditch LLVM、Incremental Compilation 这两个。

    • 很多人都对 Ditch LLVM 这个事情嗤之以鼻,认为这是不自量力,这个 issue 的讨论也比较多,已经有近 200 条回复,最近 Andrew 增加了一条回复,引用了 In Defense of Not-Invented-Here Syndrome,该文章的核心观点是如果一个技术是一个产品的核心点,那么就应该自己写,因为这样才有核心竞争力,文中的例子是 Excel 团队会自己维护一个 C 编译器。
  3. 异步的支持,目前还有还有不少需要解决的技术难点,比如:

    • async 函数挂起时,会保存所有上下文,但是在递归函数里,容易 oom
    • 无法推倒出 函数指针 是不是 async fn 的,async fn 与普通 fn 的调用方式是不一样的
  4. Donor Bounties,捐赠性悬赏,
  5. 工具链,目前还没精力,只能让社区先来做

更多细致总结可以参考:Zig Roadmap 2024 - Andrew Kelley #91 B 站搬运地址:https://www.bilibili.com/video/BV1UC4y167w9/

观点/教程

Fast-growing Zig tops Stack Overflow survey for highest-paid programming language
估计是 Bun 带动的贫富差距?!
Pool with generational references in Zig
作者在文中介绍了一种有意思的思路:当有很多指针指向同一个对象时,如何不用遍历这些指针就能让其失效,答案是用一个胖指针,包括两部分:真正的数据指针和指向对象的年龄,当指针的年龄和指向对象的年龄不一致时,即认为该指针失效。
TCC RISC-V Compiler runs in the Web Browser (thanks to Zig Compiler)

TCC 是一个 64 位 RISC-V 的编译器,作者在这篇文章里利用 Zig 把它编译成 WebAssembly 放到浏览器里执行。其中的难点在于 TCC 会调用 Posix 的一些函数,比如:fopen、fprintf、strncpy、malloc 等,但是这些在 WebAssembly 里是没有的,因此需要自己实现,不过幸好 Zig 社区内已经有不少 libc 的实现了:

Building the DirectX shader compiler better than Microsoft?
Porting Zig to NetBSD
Sig Engineering - Part 2 - Progress on AccountsDB & more

Syndica 公司的 Sig,这是一款用 Zig 编写的、专注于 RPS 的 Solana 验证器客户端。在这篇文章里,他们分析了如何优化 HashMap 的性能,而且实现了一个基于本地磁盘的 Allocator。而且他们还在招聘 Zig 工程师:

项目/工具

semickolon/kirei
🌸 The prettiest keyboard software
Dok8tavo/Interfacil
Interfacil is a Zig package for making and using interfaces easily in Zig.
kamlesh-nb/azure-sdk-for-zig
Azure Sdk for Zig - Experimental
ringtailsoftware/zig-wasm-audio-framebuffer
Examples of integrating Zig and Wasm for audio and graphics on the web
cztomsik/tokamak
Server-side framework for Zig, relying heavily on dependency injection.
The-Z-Labs/cli4bofs
Command line interface for (running) BOFs
nelipuu/zbind
Zig-TypeScript binding generator 🟦 🦎
sneekyfoxx/ziggy
又又一个 Zig 版本管理工具

202311 | 传值或传引用,这是个大问题

重大事件

本月讨论比较多的就是 Zig May Pass Anything By Reference 这篇文章了。

它讲述了 Zig 里面一个比较有争议的点,函数的参数到底是传值还是传引用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
const AAAA = struct {
    foo: [100]u32,
};

fn aaaaa(a: AAAA, b: *AAAA) void {
  b.*.foo[0] = 5;

  std.debug.print("wtf: {}", .{ a.foo[0] });
}

pub fn main() !void {
    var f: AAAA = undefined;

    f.foo[0] = 0;

    aaaaa(f, &f);
}

上面这个例子修改了 b 参数的值,但是打印出来的 a 的值也被修改了。

传值的好处就是不用担心原值会被修改,传引用的好处就是可以减少数据拷贝的代价。但是 Zig 目前采用的方式是由编译器推导来决定合适的方式。这样的目的是减少程序员负担,但这实际上会给程序带来不确定性,比如上述例子。

Andrew 在 Lobster 上回复了这个问题,确实是一个比较严重的问题,一种解法是分析一个变量有多少个 alias,编译器只在确定没问题时才进行优化,但是分析一个变量的 alias 有多少不是件容易的事。

其他语言如 C/C++/Rust 等没有进行这种优化尝试,因此没有这个问题,但是 Zig 作为一个新的语言,想尝试来用一种程序员无感的方式来解决,只是目前还没有想到更完善的方案而已。

一些熟悉 Zig zen 的读者可能会觉得这违背了第一条『Communicate intent precisely』,目前来看确实是这样的,而且 core team 老早就意识到这个问题了,感兴趣的读者可以参考:

观点/教程

Zig's std.json.Parsed(T)

老朋友 openmymind 的文章,这篇文章主要讲述了使用 Zig 中的 json 库序列化后,如何更好的使用返回值,由于有一个 allocator 参数,因此比不能简单的返回 T ,作者这里定义了一个 Managed 来解决:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
pub fn Managed(comptime T: type) type {
return struct {
value: T,
arena: *std.heap.ArenaAllocator,

const Self = @This();

pub fn fromJson(parsed: std.json.Parsed(T)) Self {
	return  .{
		.arena = parsed.arena,
		.value = parsed.value,
	};
}

pub fn deinit(self: Self) void {
	const arena = self.arena;
	const allocator = arena.child_allocator;
	arena.deinit();
	allocator.destroy(arena);
}
};
}
Factor is faster than Zig!
一个有意思的案例分享。
A day with Zig

作者把之前一个 Go 的项目转成 Zig,这里介绍了一些感受,

  • 文档缺乏
  • 文件级别导入
@fieldParentPtr
@fieldParentPtr 使用的介绍
Generating documentation from zig build

作者在这篇文章里尝试在 zig build 文件中输出文档,目前步骤略微繁琐。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
const std = @import("std");

pub fn build(b: *std.Build) void {
  const exe = b.addExecutable(.{
      .name = "myprogram",
      .root_source_file = .{ .path = "src/main.zig" },
      .target = b.standardTargetOptions(.{}),
      .optimize = b.standardOptimizeOption(.{}),
  });

  b.installArtifact(exe);

  const install_docs = b.addInstallDirectory(.{
      .source_dir = exe.getEmittedDocs(),
      .install_dir = .prefix,
      .install_subdir = "docs",
  });

  const docs_step = b.step("docs", "Copy documentation artifacts to prefix path");
  docs_step.dependOn(&install_docs.step);
}
What's Zig got that C, Rust and Go don't have? (with Loris Cro)
[视频] Loris 参与的一档播客
A Simple Example of Calling a C Library from Zig
一个简明的教程,演示 Zig 如何引用 C 类库
What is the Zig philosophy on APIs and abstraction?

一个 Reddit 帖子,讨论 Zig 的在 API 设计上的哲学、理念。一个有意思的点是 Zig 不支持私有的字段,全部都是 public 的。Andrew 在 Proposal: Private Fields #9909 这个 issue 里面讨论过原因,主要根据:

  • 一个结构体的抽象,很难保证不泄漏,比如一个类型的 align、size,一个函数是否可以在 comptime 执行
  • 一个包的兼容性,应该由文档来解释
  • 增加私有字段,会增加语言的复杂度,而且这种复杂性本身是完全可以避免的

项目/工具

zig build explained – building C/C++ projects
经典文章回顾,如何使用 Zig 构建系统编译 C/C++ 项目
akhildevelops/cudaz
A Zig Cuda wrapper

202310

重大事件

观点/教程

Notes From the Field: Learning Zig
Zig 初学者的使用经验分享
Friendly Neighbor: A network service for Linux wake-on-demand, written in Zig
作者在这篇文章中分享了 用 Zig 重写之前 Ruby 写的一个网络工具,一方面是减轻资源消耗,另一方面是探索用“低级”语言来写程序。不错的案例分享。
Zig Interfaces

作者介绍了 Zig 中如何实现接口这个经常需要用到的功能。最后的实现也比较巧妙,结合 anytype*anyopaque

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
const std = @import("std");

const Writer = struct {
  // These two fields are the same as before
  ptr: *anyopaque,
  writeAllFn: *const fn (ptr: *anyopaque, data: []const u8) anyerror!void,

  // This is new
  fn init(ptr: anytype) Writer {
      const T = @TypeOf(ptr);
      const ptr_info = @typeInfo(T);

      const gen = struct {
          pub fn writeAll(pointer: *anyopaque, data: []const u8) anyerror!void {
              const self: T = @ptrCast(@alignCast(pointer));
              return ptr_info.Pointer.child.writeAll(self, data);
          }
      };

      return .{
          .ptr = ptr,
          .writeAllFn = gen.writeAll,
      };
  }

  // This is the same as before
  pub fn writeAll(self: Writer, data: []const u8) !void {
      return self.writeAllFn(self.ptr, data);
  }
};

const File = struct {
  fd: std.os.fd_t,

  fn writeAll(ptr: *anyopaque, data: []const u8) !void {
      const self: *File = @ptrCast(@alignCast(ptr));
      // os.write might not write all of `data`, we should really look at the
      // returned value, the number of bytes written, and handle cases where data
      // wasn't all written.
      _ = try std.os.write(self.fd, data);
  }

  fn writer(self: *File) Writer {
      return Writer.init(self);
  }
};

pub fn main() !void {
  var file = try std.fs.createFileAbsolute("/tmp/demo.txt", .{});
  var my_file = File{ .fd = file.handle };
  const writer = my_file.writer();
  try writer.writeAll("hello world");
}
io_uring basics: Writing a file to disk
作者演示了 io_uring 在 Go 与 Zig 中的基本使用,下面表格是一些测试数据
methodavg_timeavg_throughput
iouring_128_entries0.2756831357s3.8GB/s
iouring_1_entries0.27575404880000004s3.8GB/s
blocking0.2833337046s3.7GB/s
Zig is now also a Windows resource compiler
相当硬核的文章,作者最近给 Zig 贡献了一个大功能:支持 Windows 资源定义文件的编译,用户可以通过 zig rc 子命令来使用。
Zig 多版本管理
由于 Zig 还在快速开发迭代中,因此项目很有可能出现新版本 Zig 无法编译的情况,这篇文章介绍了一些管理多个 Zig 版本的方式。

项目/工具

zigcli
a toolkit for building command lines programs in Zig
pb2zig
Pixel Bender to Zig code translator
zigar
Enable the use of Zig code in JavaScript project
jinyus/related_post_gen
一个对常见语言进行压测的项目,项目里面有几种纯 CPU 的操作,看看哪个语言最快。
nolanderc/glsl_analyzer
Language server for GLSL (autocomplete, goto-definition, formatter, and more)

202309 | Bun 正式发布 1.0

重大事件

Bounties Damage Open Source Projects

在 2023-09-11 号,Wasmerio CEO 创建了 Support WASIX · Issue #17115,表示想赞助 Zig 开发者,让其更好地支持 WASIX 平台。

Andrew 与 Loris 在这篇文章中主要阐述了这么做为什么是伤害社区的行为:

  1. 助长竞争,牺牲合作
  2. 在软件开发的商业管理方面,悬赏是一种极为简单的方法,这可能让开发者关注短期效益,忽视长期利益,比如维护成本。

这篇文章其实很符合 Andrew 的理念,不想让过多的热钱涌入 Zig 社区,他更想保证 Zig 的独立性,这也是他们创办 Software You Can Love 的初衷。

Bun 1.0

面包终于出炉了!Bun 毫无疑问是 Zig 的明星项目,它在 2023-09-08 正式发布了 1.0 版本,这对开发者来说可能没什么太大的区别,毕竟就只是个 tag 而已,但是对于广大的用户来说,这无疑意味着可以在生产环境中去使用了。

Bun 并不简单的是个 Node.js 替代品,而是大而全的工具链:

  • Transpilers,可以直接运行 js jsx mjs ts tsx cjs 文件
  • Bundlers,可以直接替代 webpack、esbuild 等工具
  • Package managers,兼容 npm,识别 package.json 格式,可以替代:npm、yarn、
  • Testing libraries,内置 test runner,支持快照测试、模拟和代码覆盖,可以替代:jest、ts-test

观点/教程

Kiesel Devlog #1: Now passing 25% of test262
另一个 devlog,作者写了一个 JS engine 用来学习 Zig,该作者是 SerenityOS 系统中,Ladybird 浏览器 JS 引擎 LibJS 的作者
Talk: Introducing Ghostty and Some Useful Zig Patterns

Mitchell Hashimoto 在这篇文章里分享了开发终端 Ghostty 时用的 Zig 常用模式。计划在 2024 年发布 1.0

  • Comptime Interface
  • Comptime Data Table Generation
  • Comptime Type Generation
  • B 站视频地址
Learning Zig

一个 Zig 教程,写的非常易懂,推荐每个 Zig 爱好者阅读。目录

  1. Language Overview - Part 1
  2. Language Overview - Part 2
  3. Style guide
  4. Pointers
  5. Stack Memory
  6. Heap Memory & Allocators
  7. Generics
  8. Coding In Zig
  9. Conclusion
Debugging a Zig Test Failure
非常硬核的文章,作者为了调查一个文件名太长的错误,使用了 DTrace 来探测内核函数的调用,对平时的问题排查非常有帮助
Intercepting and modifying Linux system calls with ptrace
用 Zig 来封装 Dtrace,用于跟踪子进程的 syscall
Managing Zig Versions with zvm: A Technical Dive
另一个 Zig 版本管理工具,不过这次是用 Zig 写的了
When Zig Outshines Rust – Memory Efficient Enum Arrays
一个图文并茂的文章,重点推荐。作者这里通过分析 array of struct 的内存浪费情况,介绍了 Rust/Zig 中不同的解法,Zig 由于有强大的编译期元编程能力,能够更方便的实现 SoA(struct of arrays)
struct 数组的内存布局

struct 数组的内存布局

SoA 内存布局

SoA 内存布局

项目/工具

ZigBrains
A multifunctional Zig Programming Language plugin for the IDEA platform.
fulcrum-so/ziggy-pydust
A toolkit for building Python extensions in Zig.
zig-curl
Zig bindings to libcurl.
darkr4y/OffensiveZig
Some attempts at using Zig in penetration testing.
Announcing Zig support for Wasm Workers Server
Wasm Workers Server 是一个用于开发 serverless 应用的框架,近期增加了对 Zig 的支持,使用文档
dantecatalfamo/wireguard
Command line wireguard configuration manager.
iacore/libredo
Reactive signal/Dependency tracking library in Zig.
buzz-language/buzz
A small/lightweight statically typed scripting language

Zig 语言更新

Aro translate
用 Zig 写的 Aro 来替换 clang,来实现 translate-c 的功能

202308 | 0.11 正式发布

0.11 正式发布

0.11 终于在 8 月 4 号释出了,下面来看看它的一些重要改进吧。HN 讨论

Peer Type Resolution Improvements

对等类型解析算法得到改进,下面是一些在 0.10 中不能解析,但在 0.11 中可以解析的例子:

Peer TypesResolved Type
[:s]const T, []T[]const T
E!*T, ?*TE!?*T
[*c]T, @TypeOf(null)[*c]T
?u32, u8?u32
[2]u32, struct { u32, u32 }[2]u32
*const @TypeOf(.{}), []const u8[]const u8

而且现在使用 @intCast 这类 builtin 都只接受一个参数,目前类型根据上下文自动推断出来。

Multi-Object For Loops

可以同时对多个对象进行遍历:

1
2
3
4
5
6
7
8
9
// 之前
for (input) |x, i| {
    output[i] = x * 2;
}

// 现在
for (input, 0..) |x, i| {
    output[i] = x * 2;
}

@min and @max

主要有两个改动:

  1. 这两个 builtin 现在支持任意多个参数
  2. 返回的类型,会尽可能的紧凑
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
test "@min/@max refines result type" {
    const x: u8 = 20; // comptime-known
    var y: u64 = 12345;
    // Since an exact bound is comptime-known, the result must fit in a u5
    comptime assert(@TypeOf(@min(x, y)) == u5);

    var x_rt: u8 = x; // runtime-known
    // Since one argument to @min is a u8, the result must fit in a u8
    comptime assert(@TypeOf(@min(x_rt, y)) == u8);
}

@inComptime

新加的 builtin,用于判断执行是否在 comptime 环境下执行:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const global_val = blk: {
    assert(@inComptime());
    break :blk 123;
};

comptime {
    assert(@inComptime());
}

fn f() u32 {
    if (@inComptime()) {
        return 1;
    } else {
        return 2;
    }
}

test "@inComptime" {
    try expectEqual(true, comptime @inComptime());
    try expectEqual(false, @inComptime());
    try expectEqual(@as(u32, 1), comptime f());
    try expectEqual(@as(u32, 2), f());
}

类型转化相关 builtin 的重命名

之前 @xToY 形式的 builtin 现在已经改成了 @yFromX ,这样的主要好处是便于阅读(从右向左),这是草案

Tuple 类型声明

现在可以直接用无 field 名字的 struct 来声明 tuple 类型:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
test "tuple declarations" {
    const T = struct { u32, []const u8 };
    var t: T = .{ 1, "foo" };
    try expect(t[0] == 1);
    try expectEqualStrings(t[1], "foo");

    var mul = t ** 3;
    try expect(@TypeOf(mul) != T);
    try expect(mul.len == 6);
    try expect(mul[2] == 1);
    try expectEqualStrings(mul[3], "foo");

    var t2: T = .{ 2, "bar" };
    var cat = t ++ t2;
    try expect(@TypeOf(cat) != T);
    try expect(cat.len == 4);
    try expect(cat[2] == 2);
    try expectEqualStrings(cat[3], "bar");
}

之前只能用 std.meta.Tuple 函数来定义:

1
2
-    const testcases = [_]std.meta.Tuple(&[_]type{ []const u8, []const u8, bool }){
+    const testcases = [_]struct { []const u8, []const u8, bool }{

排序

现在排序算法分布两类:

  • 稳定,blocksort 算法
  • 不稳定,pdqsort 算法,它结合了随机快速排序的快速平均情况和堆排序的快速最坏情况。

与堆排的快速最差情况相结合,同时在具有特定模式的输入上达到线性时间。

Stack Unwinding

Zig 之前依赖 frame pointer 来做堆栈回卷,但它本身有些代价,因此线上环境可能会通过 -fomit-frame-pointer 将其禁用掉。

为了在这种情况下依然能够获取 panic 是的堆栈信息,Zig 现在支持了通过 DWARF unwind tables 和 MachO compact unwind information 来会恢复堆栈,详见:#15823

包管理

0.11 首次正式引入了包管理器,具体解释可以参考:Zig Build System,而且很重要一点,step 之间可以并发执行

Bootstrapping

C++ 实现的 Zig 编译器已经被彻底移除,这意味着 -fstage1 不再生效,Zig 现在只需要一个 2.4M 的 WebAssembly 文件和一个 C 编辑器即可,工作细节可以参考:Goodbye to the C++ Implementation of Zig

代码生成

虽然 Zig 编译器现在还是主要使用 LLVM 来进行代码生成,但在这次发布中,其他几个后端也有了非常大的进步:

  1. C 后端,行为测试通过 98%,而且生成的 C 代码兼容微软的 MSVC,用在了 bootstrapping 中
  2. x86 后端,行为测试通过 88%
  3. aarch64 后端,刚开始
  4. WebAssembly 后端,行为测试通过 86%,
  5. SPIR-V 后端,SPIR-V 是在 GPU 上运行的着色器(shader)和内核的字节码表示法。目前,Zig 的 SPIR-V 后端专注于为 OpenCL 内核生成代码,不过未来可能也会支持兼容 Vulkan 的着色器。

增量编译

虽然这仍是一个高度 WIP 的功能,但这一版本周期中的许多改进为编译器的增量编译功能铺平了道路。其中最重要的是 InternPool。Zig 用户大多看不到这一改动,但它为编译器带来了许多好处,其中之一就是我们现在更接近增量编译了。增量编译将是 0.12.0 发布周期的重点。

观点/教程

Error Handling In Zig
又一篇讨论错误处理的文章
Commiting Type Crimes in Zig
对 Zig 类型系统的另一种用法,有些和邱奇数类似。
Zig in 100 Seconds
Zig 宣传视频
Zig Build System & How to Build Software From Source • Andrew Kelley • GOTO 2023
Andrew 关于构建系统的视频,B 站链接Youbute
Wrap your NIF with Zig
NIF 是 Elixir 中进行 FFI 调用的方式,如果用原生 C 接口来用,会需要写很多胶水代码, 作者这里用 comptime 特性来定义了一个 make_nif_wrapper 来简化 NIF 的实现,这个技巧在与 C 项目交互时十分有用。
Types and the Zig Programming Language
matklad 对 Zig 类型系统的总结
So Long, Twitter and Reddit
Andrew 的最新文章,远离社交平台!
WTF is Zig Comptime (and Inline)
Taking off with Zig: Putting the Z in Benchmark — Double Trouble

项目/工具

202307 | 异步缺席 0.11

重大事件

Andrewk 在最新的文章 The Upcoming Release Postponed Two More Weeks and Lacks Async Functions 中指出,即将发布的 0.11 中将不会包含对异步的支持,现在异步是在 stage2-async 这个分支上来开发,但是在开发过程中,总是有其他事情出现,然后 Andrewk 就先去搞这些事情了。因此,把对异步的支持放到 0.12 上了。

另一件事是 Jacob Young Joins the Core Zig Team,Core Team 迎来了另一位全职开发者,常用 ID jacobly0,下面是他最近的提交记录:

恭喜 Core Team,又添一虎将!

观点/教程

Copy Hunting | TigerBeetle
比较有意思的文章,通过分析 LLVM 的 IR,来避免程序中不必要的 memcpy 以及如何减少 binary 体积
Taking off with Zig: Putting the Z in Benchmark
Zig 入门文章,作者使用 Zig 来实现了一个 benchmark 库,里面有作者的感受,语言小巧,主要的语言特点:独特的错误处理、显式的内存控制、comptime 执行
The New Wave of Programming Languages: Pony, Zig, Crystal, Vlang, and Julia
多种语言的对比,Zig 部分的对比:
ProsCons
Excellent low-level control over codeRelatively new and evolving
Emphasis on safety and reliabilityLimited library support
Good interoperability with other languagesSteep learning curve
Parsing timestamps and generating RFC3339 dates in Zig

Zig 标准库里有返回 unixtime 时间戳的函数,但是没有格式化函数,作者这里给出了一种实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
pub const DateTime = struct {
  year: u16,
  month: u8,
  day: u8,
  hour: u8,
  minute: u8,
  second: u8,
};

pub fn fromTimestamp(ts: u64) DateTime {
  const SECONDS_PER_DAY = 86400;
  const DAYS_PER_YEAR = 365;
  const DAYS_IN_4YEARS = 1461;
  const DAYS_IN_100YEARS = 36524;
  const DAYS_IN_400YEARS = 146097;
  const DAYS_BEFORE_EPOCH = 719468;

  const seconds_since_midnight: u64 = @rem(ts, SECONDS_PER_DAY);
  var day_n: u64 = DAYS_BEFORE_EPOCH + ts / SECONDS_PER_DAY;
  var temp: u64 = 0;

  temp = 4 * (day_n + DAYS_IN_100YEARS + 1) / DAYS_IN_400YEARS - 1;
  var year: u16 = @intCast(100 * temp);
  day_n -= DAYS_IN_100YEARS * temp + temp / 4;

  temp = 4 * (day_n + DAYS_PER_YEAR + 1) / DAYS_IN_4YEARS - 1;
  year += @intCast(temp);
  day_n -= DAYS_PER_YEAR * temp + temp / 4;

  var month: u8 = @intCast((5 * day_n + 2) / 153);
  const day: u8 = @intCast(day_n - (@as(u64, @intCast(month)) * 153 + 2) / 5 + 1);

  month += 3;
  if (month > 12) {
      month -= 12;
      year += 1;
  }

  return DateTime{
      .year = year,
      .month = month,
      .day = day,
      .hour = @intCast(seconds_since_midnight / 3600),
      .minute = @intCast(seconds_since_midnight % 3600 / 60),
      .second = @intCast(seconds_since_midnight % 60)
  };
}
Custom JSON serialization in Zig

Zig 里面虽然有很好的 JSON 序列化支持,但是有些时候我们需要自定义某个字段的解析,与 Go json.Marshaller 类似,Zig 会在序列化时查找类型的 jsonStringify 函数,通过实现这个函数就可以达到目的,文中给出了个示例:

1
2
3
4
5
6
7
8
const NumericBoolean = struct {
  value: bool,

  pub fn jsonStringify(self: NumericBoolean, out: anytype) !void {
      const json = if (self.value) "1" else "0";
      return out.print("{s}", .{json});
  }
};
Three Different Cuts
matklad 的文章,介绍了 Rust、Go、Zig 三种语言的 Cut 实现
Zig Bits 0x4: Building an HTTP client/server from scratch
介绍了 std.http 这个模块的使用
We Put a Distributed Database In the Browser – And Made a Game of It
TigerBeetle 的新花样,把数据库搬到了 Web 上。HN 讨论
Zig: great design for great optimizations
比较有意思的文章,作者比较了 Clang、Zig 对相同逻辑代码生产的 LLVM IR,Zig 生成的 IR 更加精简

项目/工具

Zig helped us move data to the Edge. Here are our impressions
Turso 公司的官博,它们公司的产品时边缘数据库,自动同步 PG 的表到 Edge 端,减少访问的时延。在这篇文章里他们介绍了使用 Zig 编写 PostgreSQL 插件的经历,得益于 translate-c ,他们可以直接从已有的 C 代码开始构建他们的产品。插件地址:pg_turso
tensorush/meduza
🦎 🧜‍♀️ Zig codebase graph generator that emits a Mermaid class diagram
AndreaOrru/zen
Experimental operating system written in Zig
EugenHotaj/zig_gpt2
GPT-2 inference engine written in Zig

202306 | Zig 要分叉了?

重大事件

一个是这个:The Zig subreddit has closed,现在 Ziggit 算是官方钦定的论坛了。

另一个是月底出来的大新闻:File for Divorce from LLVM · Issue #16270 · ziglang/zig

这个 issue 主要讨论的是把 LLVM 从 Zig 中彻底移除,动机和优势都列在里面了,这里不再赘述,这里重点说下影响:

  1. 去掉 C++/Objc 的支持,
  2. 支持的 target 会变少

从 issue 本身和 LobstersHN 上的评论看,大家主要担忧的是对 C++ 的支持。由于非常多的基础软件都是构建在 C++ 之上的,如果没有了对 C++ 的支持,那么 Zig 作为工具链这一选择的可行性就大打折扣了,要知道 Zig 之前最主要的卖点就是这个,比如:Maintain it With Zig

有人提议用 Zig 重写一个 C++ 前端不就好了?但这属于理论上可行,实际没有可操作性的,因为 C++ 太复杂了。

其实 Zig 从 0.10.0 版本开始,就一直在着手 Self-Hosted Compiler 的开发。看得出,Zig 团队一直在追求极致,从编译速度,到二进制大小(以下数字均来自 0.10.0 的 release note):

  • Wall Clock Time: 43 seconds to 40 seconds (7% faster)
  • Peak RSS: 9.6 GiB to 2.8 GiB (3.5x less memory used)
  • As a point of comparison, a stripped release build of Zig with LLVM is 169 MiB, while without LLVM (but with all the code generation backends you see here) it is 4.4 MiB.

这个 issue 在互联网上迅速引起了热烈讨论,当然少不了吃瓜群众,以至于 Andrewk 又追加了一条评论

I see a lot of speculation in this GitHub Issue from folks who are not involved in Zig in any way. I would respectfully ask you to please take such speculation elsewhere. This issue tracker is for focused technical discussion by those who are actually using Zig, today. The noise in this thread distracts from the valuable comments by users who are sharing their use cases for the relevant features of Zig.

..which, by the way, I'm one of. For example, my music player reboot branch depends on chromaprint which is, dun dun dun, C++ code.

I'm not going to simultaneously shoot myself and valuable community members in the face by yanking a load-bearing feature out from underneath us, without any kind of upgrade path. It's a bit unfortunate that the Internet has taken that narrative and run with it.

For example, one thing to explore, later - once all those boxes above are checked - is whether we can satisfy the C++ compilation use case, as well as the LLVM optimization use case, with the package manager. The results of this exploration will heavily impact the ultimate decision of whether to accept or reject this proposal.

Please, relax. Nothing is going to happen overnight, and nothing is more important than making sure our esteemed Zig users' needs are taken care of, one way or another. Whatever happens will happen in due time, with due respect for real world projects. This proposal is aspirational - something to look forward to and consider in the coming years.

微信群里不少小伙伴也在担忧这个 issue 会不会导致 Zig 的 fork,甚至灭亡。其实看了上面的评论大家就应该放心了,Zig 团队知道用户的需要,不会搬起石头砸自己的脚。

我觉得通过这个事件更能坚定我投资 Zig 的信心了,一个追求极致的团队,不需要我们吃瓜群众瞎操心,有这个时间不如去看看 Zig 的各种 backend 进展,能不能给 fix 几个 regression?!

最后,即便 Zig 这个项目夭折了,我相信通过这个学习的过程也有助于提高我们对系统编程、编译器的理解,不是吗?

观点/教程

A Note About Zig Books for the Zig Community
Loris Cro 的博客,由于现在 Zig 的关注度越来越高,一些出版社开始联系社区的人出一本 Zig 的书,Loris 这里阐述了与出版社合作的利弊,以及他也在写一本关于 Zig 的书 Intro to Zig / systems programming ,由于 Zig 还是不停的开发中,因为书中会尽量少的去涉及 stdlib 的内容。
How far away is 0.11 really?
社区用户对 0.11 版本发布时间的疑问?
Mach: providing an ecosystem of C libraries using the Zig package manager
作者在文章讲述了利用 Zig 来打包 C 依赖的优势。
The Seamstress Event Loop In Zig
Metaprogramming in Zig and parsing CSS
Embed git commit in Zig programs
把 git 的 commit id 嵌入项目中非常有助于问题排查
Problems of C, and how Zig addresses them

不错的入门资料,主要内容:

  • Comptime over Textual Replacement Preprocessing
  • Memory Management, and Zig Allocators
  • Billion dollar mistake vs Zig Optionals
  • Pointer arithmetics vs Zig Slices
  • Explicit memory alignment
  • Arrays as values
  • Error handling
  • Everything is an expression
  • C has a more complex syntax to deal with
Minimal Linux VM cross compiled with Clang and Zig
一篇有意思的文章,作者的任务是跑 Linux kernel 的 test,为了能够方便、简单的跑不同的平台,作者尝试用 Zig 的交叉编译能力来解决这个大难题
Zig dangling pointers and segfaults
I think Zig is hard…but worth it
安利 Zig 的文章,HN 讨论 http://ratfactor.com/zig/zighard_700px.jpg

项目/工具

pondzdev/duckdb

一个将 DuckDB 数据库通过 HTTP API 暴露出来的代理,主要是利用了 DuckDB C API,示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# open the database in readonly (DB must exist in this case)
$ ./duckdb-proxy --readonly db/mydatabase.duckdb

$ curl http://localhost:8012/api/1/exec \
-d '{"sql": "select version()"}'
{
"cols": [
  "version()"
],
"rows": [
  [
    "v0.8.1"
  ]
]
}
ryleelyman/seamstress
Lua monome + OSC scripting environment
Rust VS Zig benchmarks
Which programming language or compiler is faster
ziglang/shell
Shell completions for the Zig compiler.
menduz/zig
Steamwork bindings for Zig.

202305 | HTTP is built-in

重大事件

这个月主要的事情就是 HTTP server 在标准库的增加了,具体可参考:

观点/教程

Integrating Zig and SwiftUI
Mitchell 在用 Zig 实现了一个终端后,虽然没有把源码放出来,但是有了这个文章总结。
Zig Language Server And Cancellation
Matklad 对 ZLS 实现分析:如何快速响应用户的编辑命令,在作者来看,最主要是 server 端要能够及时取消已经过期的操作。
Bootstrapping Uber’s Infrastructure on arm64 with Zig
Zig 社区的老朋友 Motiejus Jakštys 在 Uber 的 arm 化过程中,探索了如何使用 Zig 提供对 Go 代码的交叉编译。
The Worst Zig Version Manager
在没有 Zig 环境的机器上,如何安装 Zig ?作者的思路是每个项目都附带一个脚本来做这件事,这种方式看似比较笨拙,但很有实用性。
Writing an init system in a language I don't know
作者介绍了使用 Zig 开发一个 init 系统的经历,文中主要吐槽了现在 Zig 的文档严重不足 🙃,但看得出,作者依旧是喜欢 Zig 的。
SIMD with Zig
作者演示了如何利用 SIMD 函数来改进 indexOf 函数
Anytype Antics
作者介绍了如何使用 anytype ,一些示例包括:Duck Type、Traits、Comptime Tagged Unions
Using Zig | My Initial Thoughts on Ziglang
视频
Zig: First Impressions
视频
(Possibly) LVGL in WebAssembly with Zig Compiler
Initial Commit: Zig Build System
Writing DNS resolver in Zig

项目/工具

Zig by Example
非常好的学习资料。Learn How to use Zig’s Standard Library, by small examples.
sleibrock/zigtoys
All about Zig + WASM and seeing what we can do
Aandreba/zigrc
Zig reference-counted pointers inspired by Rust's Rc and Arc
Hanaasagi/struct
Deserialize env vars into typesafe structs
tristanisham/minigrep
A Zig version of the Rust book's minigrep tutorial program
jsomedon/night.zig
Simple tool that just install & update zig nightly.
4imothy/termy48
A 2048 game to run in terminal
zig-html-example
一个有趣的演示,利用 comptime 来定义 HTML 中的 Tag
KilianVounckx/rayz
另一个 Raylib 的 bindings
mitchellh/zig
Objective-C runtime bindings for Zig (Zig calling ObjC).

202304 | 首次闯入 Tiobe 前 50

重大事件

在 2023 四月份的 Tiobe 指数上,Zig 排名 46,尽管 Loris 发推表示这个数字对 Zig 来说没什么实际意义,但对于多数吃瓜群众来说,这还是十分让人鼓舞的。

For people who heard about Zig just recently:

  • Zig is not 2x faster than Rust, despite what recent benchmarks might lead you to believe.
  • You won't find many Zig jobs for a few years still, despite the Tiobe stuff.
  • Don't join to the Zig community just to rant about Rust.

— Loris Cro ⚡ (@croloris) April 13, 2023

观点/教程

When should I use an UNTAGGED Union?
Loris 的文章,作者利用访问 untagged union 的未赋值字段是一种 safety-checked UB 的行为,来解决数组成员被重新赋值过的情况。
Data driven polymorphism
作者用 Zig 来实现 Clojure 语言中的 defmulti,以达到『动态派发』的效果
Testing and Files as Structs
作者演示了一个文件作为 struct 的效果,这样导入时就可以用 const Node = @import("Node.zig") 的方式了。
Sneaky Error Payloads
一种在错误中携带上下文信息的方式,上一期的月报也有类似讨论。 Errors and Zig
Regular Expressions in Zig
由于 Zig 现在不支持 C 中的 bitfields,因此无法直接使用 Posix 的 regex.h ,这篇文章介绍了一种解决方法。
Zig Build System
对 Zig build 系统的介绍
Reasonable Bootstrap
探讨了编译器如何实现自举的方式
Data Oriented Parallel Value Interner
Matklad 探讨了如何实现一个高性能的 Interner
TigerStyle! (Or How To Design Safer Systems in Less Time)
Systems Distributed 23 视频。B 站链接
What Is a Database?
Systems Distributed 23 视频,B 站链接,作者博客:Scattered Thoughts

项目/工具

Coming Soon to a Zig Near You: HTTP Client
对标准库 std.http 的介绍。
Zig Bits 0x3: Mastering project management in Zig
介绍了如何更好地维护一个 Zig 项目,包括:新增依赖、增加测试覆盖率、增加文档、基于 GitHub Action 做持续集成等。
ityonemo/zigler
zig nifs in elixir
Ziggifying Kilo
使用 Zig 重写 kilo 编辑器,目前仅能在 Linux 上运行
jakubgiesler/VecZig
Vector implementation in Zig
b0bleet/zvisor
Zvisor is an open-source hypervisor written in the Zig programming language, which provides a modern and efficient approach to systems programming.

202303 | 并发编译

观点/教程

Creating arbitrary error values by using error.Something syntax

下面两种方式是等价的:

1
2
const err = error.FileNotFound;
const err = (error {FileNotFound}).FileNotFound;
Errors and Zig

主要讲述了 Zig 中如何处理错误,如何携带上下文信息

1
2
3
4
5
6
7
8
9
var x = try thingThatCouldFail();

if (thingThatCouldFail()) |good_value| {
  x = good_value;
  break;
} else |_| {
  // do something that should fix it for the next time
  tries -= 1;
}
Zig Bits 0x2: Using defer to defeat memory leaks
主要介绍了如何探测内存泄漏,如何用 defer 来规避
Naive parallel map implementation in Rust and Zig
The Curious Case of a Memory Leak in a Zig program
When Zig is safer and faster than Rust
比较有趣的文章,作者使用 unsafe rust 与 zig 来实现一个 bytecode 解释器,比较重要的一点是具备 mark-sweep 的 GC 功能。
Meet Zig: The modern alternative to C | InfoWorld
一篇科普 Zig 的文章
Cross-Compiling and packaging C, Go and Zig projects with Nix
文章介绍了如何 利用 Nix 进行交叉编译,对于 C 依赖,作者是通过修改 pkg-config*.pc 来支持的
Zig Quirks
介绍 Zig 的一些特点
Zig And Rust

项目/工具

macovedj/doink
Making WebAssembly Components with Zig
craftlinks/zig_learn_opengl
Follow the Learn-OpenGL book using Zig
b0bleet/zvisor
Zig-based Hypervisor (WIP)
flouthoc/ztick
Tiny desktop utility to keep notes
ringtailsoftware/zig-wasm-audio-framebuffer
Examples of integrating Zig and Wasm for audio and graphics on the web

Zig 语言更新

zig build: run steps in parallel #14647

一个比较大的更新,编译支持了并发。由于改动比较大,该 PR 合并后出现了一些 bug,一个影响比较大的是 test 的构建方式不一样了。

1
2
-    test_step.dependOn(&exe_tests.step);
+    test_step.dependOn(&exe_tests.run().step);

之前老方式写的 test_step 在新版本不会再执行了。这样对于 Build 系统来说其实是更合理了。

现在 addTestaddExecutable 一样,输出都是 CompileStep ,它默认不会执行,需要调用 run() 拿到 run step 才可以。

Zig 构建系统介绍

构建系统其实是 Zig 生态中比较重要的一环,和 Rust 不同,Zig 即是一门编程语言,也是一系列工具链,比如编译 Zig 时,没有 zigc 这个二进制文件,用的是 zig build-exebuild-lib

build.zig 的作用就是提供了一套声明式 API 来构造 build-exe 的参数。这里面有一个核心概念: Step ,它构成了一个有向无环图,用来驱动整个编译过程。

每个 Step 做的事情是由 MakeFn 定义的,它的签名是:

1
pub const MakeFn = *const fn (self: *Step, prog_node: *std.Progress.Node) anyerror!void;

但一般来说,我们并不需要自己去实现 MakeFn,内置的 Step 已经可以满足大部分需求,比如:

  • CompileStep 编译二进制、动态链接库、静态链接库
  • InstallArtifactStep 把编译生成的文件复制到 zig-out
  • ObjCopyStep 执行 objcopy 命令
  • OptionsStep 可以用来定义编译时的一些常量,作为 module 被当前程序使用,比如把当前项目的构建时间、Git 信息写入到代码中。类似于 Go 里面的 go build -ldflags="-X 'package_path.variable_name=new_value'"
  • RunStep 执行二进制

Step 中有一类比较特殊,称为 TopLevelStep(简称 TLS),它们可以直接通过 zig build {topLevelStep} 的方式来执行,Zig 默认有两个 TLS:

  • install,安装二进制
  • uninstall,卸载二进制
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
const exe = b.addExecutable(.{
    .name = "awesome",
    .root_source_file = .{ .path = "src/main.zig" },
    .target = target,
    .optimize = optimize,
});
const run_cmd = exe.run();
if (b.args) |args| {
    run_cmd.addArgs(args);
}
const run_step = b.step("run-"  , "Run " ++ name);
run_step.dependOn(&run_cmd.step);

上面这段代码就定义了一个 TLS: run ,它依赖 exe 的执行 step,exe 本身又是个编译 step,因此在 zig build run 时,会依次执行:

1
CompileStep --> RunStep --> TLS

Zig 的编译系统设计的还是挺巧妙的,而且 build.zig 是新人接触 Zig 是打交道最多的代码,如果搞不清它的执行过程,一方面心里比较难受,另一实际方面是影响问题排查。

如果读者还是对 build.zig 有所困惑,可以参考下面这两个文章,虽然有些过时,但是原理是一样的:

202302 | 精益求精的包管理

包管理器进展

包管理器自 #14265 合并后一直在不断推进,以下两个是最主要的改变:

也欢迎大家给自己熟悉的 C/C++ 项目提 PR 让其支持 zig build,让构建不再那么痛苦。

观点/教程

202301 | 包管理来了

0.10.1 版本发布

一个小版本,主要是 bugfix。最主要的功能是:Package Manager MVP,Zig 终于开始支持包管理了! 不过才刚刚开始,有一个面板来跟踪相关 issue 进度。使用的配置文件是 build.zig.ini ,格式如下:

[package]
name=libffmpeg
version=5.1.2

[dependency]
name=libz
url=https://github.com/andrewrk/libz/archive/f0e53cc2391741034b144a2c2076ed8a9937b29b.tar.gz
hash=c9b30cffc40999d2c078ff350cbcee642970a224fe123c756d0892f876cf1aae

[dependency]
name=libmp3lame
url=https://github.com/andrewrk/libmp3lame/archive/497568e670bfeb14ab6ef47fb6459a2251358e43.tar.gz
hash=9ba4f49895b174a3f918d489238acbc146bd393575062b2e3be33488b688e36f

build.zig 引用方式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
const std = @import("std");

pub fn build(b: *std.build.Builder) void {
    const target = b.standardTargetOptions(.{});
    const mode = b.standardReleaseOptions();

    const libz_dep = b.dependency("libz", .{});
    const libmp3lame_dep = b.dependency("libmp3lame", .{});

    const lib = b.addStaticLibrary("ffmpeg", null);
    lib.setTarget(target);
    lib.setBuildMode(mode);
    lib.linkLibrary(libz_dep.artifact("z"));
    lib.linkLibrary(libmp3lame_dep.artifact("mp3lame"));
    lib.linkLibC();
    lib.addIncludePath(".");
    lib.install();
}

其他关注点:

  • LLVM 升级到 15.0.7
  • 是 0.10.x 的最后一个 release 版本

观点/教程

Code study: interface idioms/patterns in zig standard libraries
由于 Zig 目前还不支持接口抽空,本文介绍了标准库中来实现类似功能的五种方式
A Zig Diary
作者分享了对 Zig 的使用体验
Why Accounting Needs Its Own Database with Joran Greef of Tiger Beetle
播客分享
Crossplatform JNI builds with Zig
又一个使用 Zig 作为交叉编译的例子

项目/工具

Introducing ⚡zap⚡ - blazingly fast backends in zig
Zap 是 Zig 对 facil.io - The C Web Application Framework 的封装,本文算是对它的宣传。
Indexing every Zig for great justice
本文介绍了另一种语言服务器协议(LSP):SCIP,并用 zig 实现。项目处于早期阶段。
dantecatalfamo/zig-git
Implementing git structures and functions in zig
axiomhq/zig-hyperloglog
Zig library for HyperLogLog estimation
This Week In Zig
一个介绍 Zig 的周刊,主要是 master 分支上的改动

202211 | 0.10 横空出世

0.10.0 Release Notes

本月最大的事情就是 0.10 版本发布了,主要功能就是 self-hosted compiler,也称为『自举』,即可以用 Zig 来写 Zig 编译器,自举之所以对于一门语言如此重要,主要在于,这说明了该语言可以处理足够复杂的系统,不再只是玩具而已。编译的提升:

  • Wall Clock Time: 43 seconds to 40 seconds (7% faster)
  • Peak RSS: 9.6 GiB to 2.8 GiB (3.5x less memory used)

赶紧升级吧,少年!

zigcc 中文社区微信群

欢迎喜欢 Zig 的小伙伴加入!

ZigCC 微信群二维码

观点/教程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
const Animal = union(enum){
   cat: Cat,
   dog: Dog,
   snake: Snake,

   pub fn talk(self: Animal) void {
      switch (self) {
         .snake => std.debug.print("Ssss~~~", .{}),
         inline else => |case| case.talk(),
      }
   }
};

项目/工具

Zig 语言更新

202210 | 0.10 蓄势待发

观点/教程

项目/工具

Zig 语言更新

202209 | 锋芒毕露

Zig VS Rust 火花

在 9/10 号左右,在 Twitter 上牵起了一小波关于 Zig VS Rust 的小火花,以至于最后 Zig 创始人 Andrew Kelley 发推表示 Let us exist。这里稍微整理下这件事情的过程: 本次事件主要涉及两个人:

  • Rust 核心贡献者: Patrick Walton
  • Zig 社区 VP: Loris Cro

时间线

  • 8/26 号,一篇关于 wasm 2 Game Jam 的分析报告中,使用 Zig 的人数最多
  • 9/9 号,这篇报告在 HackerNews 上引起了热烈讨论,其中 Walton 在多处回复中表示 Zig 语言的劣势,并称

    It’s perfectly reasonable to take the position that it’s deeply problematic for a language aiming for wide use in 2022 to not be memory safe. There’s no requirement that you “focus on tradeoffs”, especially since real people get hurt by memory safety problems.

  • Loris 回复到

    I think you’re actively hurting the project that you care about in your ineffective crusade, but hey, don’t let me stop you.

  • 9/10 号,有人发推对 tigerbeetle 内存分配方式表示好奇:所有内存必须在启动时静态分配好
  • Walton 回复到

    The weird thing is that this is used as an example of why you supposedly don’t need language-enforced memory safety.

    But that literally is language-enforced memory safety! Just way more restrictive than what Rust has: if you hate the borrow check, try the “no heap” check…

    This is wrong because you can still have UAF from freed stack frames.

  • Loris 针对 Walton 的回复说了句“What a boring, useless take.(原推的回复已经被 Loris 删除了,可以在这里看到历史):
  • Walton 发推表示在 2022 年,所有语言都应该是内存安全,应该算是『编程语言界的共识』,并称 Zig 是行业的一大退步 😅
  • Loris 专门发了一个 Twitter thread 来阐述『软件的目标不仅仅是内存安全,更重要的是正确』。比如 tigerbeetle 这里提到的。而且即便内存安全,也可能发生 OOM

总结

上面的链接比较多,这里稍微总结下这次争论的问题:

Rust 用户觉得 Zig 不是内存安全的语言,Zig 认为 Rust 的语言过度复杂,这反而会导致程序复杂度挺升,导致程序产生错误行为

使用 Zig 的人大概率也是 Rust 用户,之所以有了安全的 Rust,还来选 Zig,笔者觉得大概率就是本次争论的观点,Rust 过于复杂,导致程序员不仅仅要考虑业务行为,还需要按照 Rust 的风格来编程,这加剧了程序出错的可能性。

观点/教程

项目/工具

Zig 语言更新

202208 | stage2 默认开启

观点/教程

项目/工具

Zig 语言更新