March 13, 2024
之前 ZigCC 所有项目都是托管在 GitHub 之上,网页基于 Pages 构建,域名自然也就是 github.io 的,虽然 GitHub 提供了很多利于开发者的服务,但过于依赖 GitHub 这种商业公司,还是不利于 ZigCC 的长远发展,域名是其中很重要一个,有了独立域名,网页托管选择就多了,比如 Cloudflare Pages。
另一个大家比较关心的问题就是 0.12 的发版,虽然 milestone 显示还剩 10 来个 open 的 issue,但是这只是个幌子,核心团队还是有可能随时 delay。不过从剩下的 issue 来分析,主要问题还剩两大类:
新功能看来是已经 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 的文章,在这里他针对以下几点进行了语言对比:
node_modules
是业界经常提到的一个反面例子,一般支持精简的人会认为,must-not-allocate
的注解, 这样高级语言里,也可以保值某些操作不会有内存分配。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