Writergate:Zig 输入/输出接口重构

Hacker News Top 工具

摘要

Writergate 是 Zig 输入/输出接口重构的非正式名称,它用具体类型和虚表替换泛型类型,以解决设计缺陷并实现异步输入/输出等功能。这些更改是破坏性的,需要更新现有代码。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/08/15 15:35

# Writergate 来源:https://alexrios.me/blog/writergate/ **Writergate** 是 Zig 于 2023 年底开始、在 2025 年 8 月随着 `GenericWriter`、`GenericReader`、`AnyWriter` 和 `AnyReader` 完全移除而达到高潮的 I/O 接口大修的非正式名称。如果你最近接触过 Zig 的 I/O 代码,你肯定感受到了其影响。 ## 变化了什么 旧 API 使用带类型参数的泛型类型: ```zig // 旧版(已移除) const stdout = std.io.getStdOut(); const writer = stdout.writer(); try writer.print("Hello {s}\n", .{"world"}); ``` 新 API 使用带虚函数表和显式缓冲的具体类型: ```zig // 新版 (0.15+) const stdout = std.fs.File.stdout(); var buffer: [4096]u8 = undefined; var file_writer = stdout.writer(&buffer); const writer = &file_writer.interface; defer writer.flush() catch {}; try writer.print("Hello {s}\n", .{"world"}); ``` 主要破坏性变更: 1. **命名空间**:`std.io` 变为 `std.Io` 2. **缓冲机制**:由调用方提供缓冲区,而非实现方 3. **类型**:Writer/Reader 是带虚函数表的具体类型,而非泛型 4. **刷新**:必须显式刷新;否则输出可能不会显示 ## 为何重要 旧的泛型设计“污染”了 API:任何接受 writer 的函数都会变成泛型,进而迫使所有包含它的结构体也变成泛型。Andrew Kelley 在 Writergate PR (https://github.com/ziglang/zig/pull/24329) 中将旧接口描述为“污染包含它们的结构体”。我见过这种模式感染整个代码库:一个 `anytype` 参数扩散,直到你半个库都变成了泛型。这限制了 API 的可复用性并损害了编译时间。 Zig 0.16 中的后续改进将 I/O 视同内存分配:代码依赖 `Io` 实例的方式与依赖 `Allocator` 相同。这使得: - **异步**:0.16 版本的 `Io` 虚函数表包含了 `async`、`await` 和 `cancel` 原语。相同代码在今天可以与线程池一起工作,待 `io_uring` 或 `kqueue` 后端成熟后亦可使用。 - **性能**:缓冲位于虚函数表之上,因此缓冲写入不会在热路径上触及虚分派。 - **精确错误**:不再到处使用 `anyerror`;后端操作携带具体的错误集合;Writer/Reader 接口暴露简洁的 `WriteFailed`/`ReadFailed`,而细节保留在具体实现中。 ## 虚函数表架构 新系统分为三个层级: ```text Io (Backend) ← 线程化、事件驱动、Uring... (0.16) ↓ Io.Writer / Io.Reader ← drain、stream、flush、rebase ↓ File.Writer / File.Reader ← 具体实现 ``` 自定义 writer 嵌入接口,并通过 `@fieldParentPtr` 恢复父结构体: ```zig pub const MyWriter = struct { my_data: u32, interface: std.Io.Writer, fn drain(io_w: *std.Io.Writer, data: []const []const u8, splat: usize) std.Io.Writer.Error!usize { const self: *MyWriter = @alignCast(@fieldParentPtr("interface", io_w)); _ = self.my_data; // 可以访问父结构体字段 // 处理缓冲区和传入的数据,返回消耗的字节数。 // 每个切片计数一次,除最后一个:它重复 splat 次。 io_w.end = 0; var total: usize = 0; for (data[0 .. data.len - 1]) |slice| total += slice.len; total += data[data.len - 1].len * splat; return total; } }; ``` ## 常见陷阱 我至少都碰到过一次: - **忘记刷新**:退出时仍留在缓冲区里的字节会静默丢失。一个短程序运行,什么也不打印,然后成功退出。很令人抓狂。 - **格式说明符**:对于有 `format` 方法的类型使用 `"{f}"`,而非 `"{}"` - **标准流**:`std.io.getStdOut()` 现在是 `std.fs.File.stdout()` - **复制接口**:永远不要复制嵌入在父实现中的接口(`var w = impl.interface`);始终使用指针(`&impl.interface`)。虚函数表通过 `@fieldParentPtr` 恢复父对象,而复制会破坏这一点。像 `Writer.fixed` 这样的独立 writer 是纯值,可以正常复制。详见迁移指南 (https://alexrios.me/blog/writergate-migration)。 ## 另请参阅 - Writergate 第一部分:泛型 I/O 的问题 (https://alexrios.me/blog/writergate-the-problem) - Writergate 第二部分:新架构 (https://alexrios.me/blog/writergate-architecture) - Writergate 第三部分:迁移模式 (https://alexrios.me/blog/writergate-migration) - Writergate PR #24329 (https://github.com/ziglang/zig/pull/24329) - Zig 0.15.1 发布说明 (https://ziglang.org/download/0.15.1/release-notes.html) - openmymind.net: Zig 的新 Writer (https://www.openmymind.net/Zigs-New-Writer/)

相似文章

Zig 0.16 中的异步 I/O:今日视角

Lobsters Hottest

Zig 0.16 推出了新的 std.Io 接口,用于跨平台 I/O。zio 库通过栈式协程和操作系统级异步 API 提供了完整的异步实现,无需每个任务一个线程即可实现高效的并发任务。

构建系统重构

Lobsters Hottest

Zig 构建系统已经重构,将配置器和制造器进程分离,支持缓存、发布模式编译,并且'zig build'命令速度提升高达90%。这一变化提高了性能,并允许构建系统在不减速的情况下增加功能。

wio:窗口化输入/输出

Lobsters Hottest

wio 是一个 Zig 平台抽象库,负责处理窗口管理、事件、剪贴板、音频以及图形上下文创建(OpenGL、Vulkan),支持 Windows、macOS、Linux、Android 和 WebAssembly。

Zig 的 Io.Threaded 很巧妙

Lobsters Hottest

文章讨论了 Zig 的 std.Io.Threaded,这是 Zig Io 接口的一种实现,使用阻塞系统调用并通过信号支持取消,同时对比了并发与并行。

重返Zig

Lobsters Hottest

作者描述了从Zig到Rust再回到Zig的历程,探讨了编程语言中稳定性与表达力之间的权衡。