返回

文章详情

Writergate: Zig I/O接口大修

Hacker News2026年8月15日 13:59

Writergate是Zig的I/O接口大修的非正式名称,该项目始于2023年底,并于2025年8月以完全删除GenericWriter、GenericReader、AnyWriter和AnyReader而告终。如果你最近接触过Zig I/O代码,你一定感受到了这一变化。有什么变化 旧API使用带有类型参数的泛型类型: // 旧(已移除) const stdout = std . io . getStdOut () ; const writer = stdout . writer () ; try writer . print ( "Hello {s} \n " , . { "world" }) ; 新API使用带有虚表和显式缓冲的具体类型: // 新(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" }) ; 破坏性变化:命名空间:std.io变为std.Io 缓冲:调用者提供缓冲区,而不是实现 类型:Writer/Reader是具体类型,带有虚表,而不是泛型 刷新:你必须显式刷新;没有它,输出可能不会出现 这有什么重要性 旧的泛型设计使API变得混乱:接受编写器的任何函数都变得泛型,迫使所有包含的结构体也变为泛型。Andrew Kelley的Writergate PR描述了旧接口为“污染了包含它们的结构体”。我见过这种模式感染整个代码库:一个anytype参数传播,直到你库中的一半都变得泛型。这限制了API的可重用性并影响了编译时间。Zig 0.16中的后续版本将I/O视为内存分配:代码依赖于Io实例,就像它依赖于分配器一样。这使得:异步:0.16 Io虚表包括async、await和cancel原语。今天,相同的代码可以使用线程池、io_uring或kqueue,随着这些后端的成熟而运作。性能:缓冲位于虚表之上,因此缓冲写入在热路径中不会触及虚拟调度。精确错误:而不是到处使用任何错误,后端操作携带特定的错误集;Writer/Reader接口提供紧凑的WriteFailed / ReadFailed,具体实现保留详细信息。虚表架构 新系统有三个层次: Io (后端) ← 线程化、事件化、Uring... (0.16) ↓ Io.Writer / Io.Reader ← drain, stream, flush, rebase ↓ File.Writer / File.Reader ← 具体实现 自定义编写器嵌入接口,并通过@fieldParentPtr恢复父项: 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 ; } } ; 常见误区 我至少遇到过以下所有问题: 忘记刷新:程序退出时缓冲区仍有字节会悄悄丢失。一个短程序运行,什么都不打印,成功退出。令人烦恼。 格式说明符:对于具有格式化方法的类型,请使用"{f}",而不是"{}" 标准流:std.io.getStdOut()现在是std.fs.File.stdout() 复制接口:永远不要复制嵌入在父实现中的接口(var w = impl.interface);总是使用指针(&impl.interface)。虚表通过@fieldParentPtr恢复父项,复制会破坏这一点。像Writer.fixed这样的独立编写器是普通值并且可以正常复制。有关详细信息,请参阅迁移指南。 另请参见 Writergate第1部分:与泛型I/O的问题 Writergate第2部分:新架构 Writergate第3部分:迁移模式 Writergate PR #24329 Zig 0.15.1发布说明 openmymind.net:Zig的新编写器

赞助内容

NordVPN Next-gen Antivirus

本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。

请我喝杯咖啡