202403 | ziglang.cc 正式上线
重大事件
之前 ZigCC 所有项目都是托管在 GitHub 之上,网页基于 Pages 构建,域名自然也就是 github.io 的,虽然 GitHub 提供了很多利于开发者的服务,但过于依赖 GitHub 这种商业公司,还是不利于 ZigCC 的长远发展,域名是其中很重要一个,有了独立域名,网页托管选择就多了,比如 Cloudflare Pages。
另一个大家比较关心的问题就是 0.12 的发版,虽然 milestone 显示还剩 10 来个 open 的 issue,但是这只是个幌子,核心团队还是有可能随时 delay。不过从剩下的 issue 来分析,主要问题还剩两大类:
- 构建系统完善
- 修复之前功能带来的回顾问题(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
,在编译时会报下面的错误:
|
|
虽然 a
是个运行时的值,但是 errdefer comptime unreachable
不关心这个,只要 Zig 编译器开始生成 ErrorSet 相关代码,
编译就会报错,去掉上面的 if 代码块后,测试就可以正常执行。一个实际的例子:
- std.hash_map: fix pointer lock safety false positive by andrewrk · Pull Request #19364 · ziglang/zig
|
|
可以看到, 这么修改后,就可以保证 map.deinit(allocator)
语句之前没有错误可能产生!读者可以细细品味一下这个用法。
另一个小技巧是 errdefer 竟然支持错误捕获,即下面这种用法:
|
|
- 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