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