202406 | 0.13 来了

重大事件

2024-06-07,0.13.0 发布,历时不足 2 个月,有 73 位贡献者,一共进行了 415 次提交! 这是一个相对较短的发布周期,主要原因是工具链升级,例如升级到 LLVM 18

一个比较大的 Breaking changes 是 ComptimeStringMap 被重命名为了 StaticStringMap , 使用方式也发生了变化,更多细节可参考:#19682

1
const map = std.StaticStringMap(T).initComptime(kvs_list);

0.14.0 发布周期的主题将是编译速度。将在 0.14.0 发布周期中努力实现一些即将到来的里程碑:

  • 使 x86 后端成为调试模式的默认后端。
  • COFF 的链接器支持。消除对 LLVM LLD 的依赖。
  • 启用增量编译以实现快速重建。
  • 将并发引入语义分析,进一步提高编译速度。

观点/教程

Leveraging Zig's Allocators

老朋友 openmymind 的又一篇好文章:如何利用 Zig 的 Allocator 来实现请求级别的内存分配。 Zig Allocator 的最佳应用。这里它的中文翻译。

 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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
const FallbackAllocator = struct {
  primary: Allocator,
  fallback: Allocator,
  fba: *std.heap.FixedBufferAllocator,

  pub fn allocator(self: *FallbackAllocator) Allocator {
    return .{
      .ptr = self,
      .vtable = &.{.alloc = alloc, .resize = resize, .free = free},
    };
  }

  fn alloc(ctx: *anyopaque, len: usize, ptr_align: u8, ra: usize) ?[*]u8 {
    const self: *FallbackAllocator = @ptrCast(@alignCast(ctx));
    return self.primary.rawAlloc(len, ptr_align, ra)
           orelse self.fallback.rawAlloc(len, ptr_align, ra);
  }

  fn resize(ctx: *anyopaque, buf: []u8, buf_align: u8, new_len: usize, ra: usize) bool {
    const self: *FallbackAllocator = @ptrCast(@alignCast(ctx));
    if (self.fba.ownsPtr(buf.ptr)) {
      if (self.primary.rawResize(buf, buf_align, new_len, ra)) {
        return true;
      }
    }
    return self.fallback.rawResize(buf, buf_align, new_len, ra);
  }

  fn free(_: *anyopaque, _: []u8, _: u8, _: usize) void {
    // we noop this since, in our specific case, we know
    // the fallback is an arena, which won't free individual items
  }
};

fn run(worker: *Worker) void {
  const allocator = worker.server.allocator;

  // this is the underlying memory for our FixedBufferAllocator
  const buf = try allocator.alloc(u8, 8192);
  defer allocator.free(buf);

  var fba = std.heap.FixedBufferAllocator.init(buf);

  while (queue.pop()) |conn| {
    defer fba.reset();

    var arena = std.heap.ArenaAllocator.init(allocator);
    defer arena.deinit();

    var fallback = FallbackAllocator{
      .fba = &fba,
      .primary = fba.allocator(),
      .fallback = arena.allocator(),
    };

    const action = worker.route(conn.req.url);
    action(fallback.allocator(), conn.req, conn.res) catch { // TODO: 500 };
    worker.write(conn.res);
  }
}

On Zig vs Rust at work and the choice we made

这篇文章作者描述了所在公司在改造老 C/C++ 项目时,为什么选择了 Zig 而不是 Rust。 重写的项目运行在多个平台上(Web、移动端、VR 设备),因此最靠谱的方案就是暴露一个 C API,然后通过 FFI 来调用。在做决策时,重点关注以下两点:

  • 新语言与 C 的交互性
  • 工程师扩展代码库的难易程度(如招聘和维护)

下面是 Zig VS Rust 的优势:

RustZig
成熟度更流行、稳定;使用范围更广
包管理Cargo 业界领先比 Makefile 好用
安全内存安全
SIMDnightly 支持通过 Vector 类型支持
C 交互性生态丰富编译器本身就是 C 编译器,这样就可以逐步重写项目

如果只是根据上面的比较,貌似还看不出选择 Zig 的动机,因此作者在最后提到:

Zig 大大减少了移植现有代码库和确保所有平台兼容性所需的时间和精力。我们的团队无法相信 Rust 能让这一切变得如此简单。

相信这也是大部分人选择 Zig 的原因:简洁、高效。

Packing some Zig before going for the countryside

作者列举的一些 Zig 学习资料、常用类库。该作者的另一篇文章也有不少资料:2024 Collection of Zig resources

Why I am not yet ready to switch to Zig from Rust

Turso CTO 的一篇文章,他本身是个资深 C 程序员,而且也比较喜欢 C,但 C 不是一门安全的语言,因此通过 Rust,作者可以避免 写出 SIGSEGVS 的代码,尽管 Rust 是门复杂的语言,但是因为它有完善的生态(有大公司如微软、谷歌等做背书)、已经内存安全等特点, 已经是作者系统编程的首选。

对于 Zig,尽管作者也表达了喜欢,但由于 Zig 的生态不完善,没有足够多的学习资料,因此作者觉得目前阶段选择 Zig 并不会带来 工作上生产力的提高。这一点说的无可厚非,试想一下,如果一个项目所有的依赖都需要自己做,工作效率确实很难提上去。

但是笔者有一点不能理解,就是该作者觉得 comptime 不好用,相比之下,他更喜欢 C 里面的宏。comptime 就是为了 C 宏的不足 而诞生的,社区普遍也觉得 comptime 是个新颖的设计,笔者也是第一次见到这个观点,只能说,萝卜青菜,各有所爱。

其他社区的一些讨论:LobstersHacker News

项目/工具

malcolmstill/zware
Zig WebAssembly Runtime Engine
Cloudef/zig-aio
io_uring like asynchronous API and coroutine powered IO tasks for zig