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 有所困惑,可以参考下面这两个文章,虽然有些过时,但是原理是一样的: