Zig 语言中文社区

202403 | ziglang.cc 正式上线

March 13, 2024

ZigCC

Table of Contents

重大事件

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,之前的实现问题很多。比如:

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

Why does an extraneous build step make my Zig app 10x faster?
作者在这篇文章里分享了自己遇到的一个很有意思的问题, 同一份代码,执行方式不同,竟然有不同的耗时。最小复现代码:

// 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});
}

两种执行方式:

$ 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 除了做资源回收外,其他的一些惯用法,里面有几个有趣的点:

errdefer comptime unreachable

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

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

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 ,在编译时会报下面的错误:

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

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

   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 竟然支持错误捕获,即下面这种用法:

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. 一个例子:

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 开发。

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

Zig 语言更新

202402 | Zig 2024 Roadmap 新鲜出炉
202404 | Zig 0.12.0 正式释出