Skip to content

Commit 623d104

Browse files
committed
another new post about writer
1 parent 07ed261 commit 623d104

File tree

1 file changed

+80
-0
lines changed

1 file changed

+80
-0
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
---
2+
.title = "Zig 0.15 中的新 Writer 接口",
3+
.date = @date("2025-09-18T08:58:33+0800"),
4+
.author = "刘家财",
5+
.layout = "post.shtml",
6+
---
7+
8+
> 原文:https://www.openmymind.net/Zigs-New-Writer/
9+
10+
正如你可能听说过的,Zig的`Io`命名空间正在重新设计。最终,这将意味着重新引入异步。作为第一步,Writer和Reader接口以及一些相关代码已经过改进。
11+
12+
> 这篇文章是根据Zig的2025年7月中旬开发版本撰写的。它不适用于Zig 0.14.x(或任何以前的版本),并且可能随着更多Io命名空间的返工而过时。
13+
14+
不久前,我写了一篇博客文章,[Zig's Writers](https://www.openmymind.net/In-Zig-Whats-a-Writer/)试图解释Zig的 Writer 。充其量,我会将当前状态描述为“混淆”两个 Writer 界面,同时经常处理`anytype`. .和虽然`anytype`很方便,它缺乏开发人员人体工程学。此外,目前的设计对于一些常见情况存在重大性能问题。
15+
16+
新`Writer`接口是`std.Io.Writer`. .至少,实现必须提供`drain`功能。其签名看起来像:
17+
18+
```zig
19+
fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize
20+
```
21+
22+
你可能会惊讶于这是自定义编写者需要实现的方法。它不仅需要一个字符串数组,但那是什么`splat`参数?像我一样,你可能期望一个更简单的`write`方法:
23+
24+
```zig
25+
fn write(w: *Writer, data: []const u8) Error!usize
26+
```
27+
28+
事实证明`std.Io.Writer`有内置的缓冲。例如,如果我们想要一个`Writer`为 A`std.fs.File`我们需要提供缓冲:
29+
30+
```zig
31+
var buffer: [1024]u8 = undefined; var writer = my_file.writer(&buffer);
32+
```
33+
34+
当然,如果我们不想要缓冲,我们总能传递一个空的缓冲区:
35+
36+
```zig
37+
var writer = my_file.writer(&.{});
38+
```
39+
40+
这就解释了为什么自定义编写者需要实现一个`drain`方法,而不是更简单的东西,如`write`. .
41+
42+
最简单的实现方法`drain`,在进行这次更大的大修时,Zig标准库已经升级了很多,是:
43+
44+
```zig
45+
fn drain(io_w: *std.Io.Writer, data: []const []const u8, splat: usize) !usize { _ = splat; const self: *@This() = @fieldParentPtr("interface", io_w); return self.writeAll(data[0]) catch return error.WriteFailed; }
46+
```
47+
48+
我们忽略了`splat`参数,只需在中写第一个值`data`(`data.len > 0`保证是真实的)。这 转`drain`进入什么更简单`write`方法会看起来像。因为我们返回写入的字节长度,`std.Io.Writer`会知道我们可能没有写所有数据并调用`drain`再次,如果有必要,与其余的数据。
49+
50+
> 如果你被调用混淆了`@fieldParentPtr`[upcoming linked list changes](https://www.openmymind.net/Zigs-New-LinkedList-API/)查看我关于即将到来的链接列表更改的帖子。
51+
52+
实际执行`drain`为`File`是一个非平凡的〜150行代码。它具有特定于平台的代码,[并在可能的情况下利用矢量I/O。](https://www.openmymind.net/TCP-Server-In-Zig-Part-3-Minimizing-Writes-and-Reads/#writev)显然,提供简单的实现或更优化的实现具有灵活性。
53+
54+
就像当前状态,当你做`file.writer(&buffer)`你没有得到一个`std.Io.Writer`. .相反,你得到一个`File.Writer`. .获取实际`std.Io.Writer`,你需要访问`interface`领域。这只是一个惯例,但期望它在整个标准和第三方图书馆中使用。准备好看很多`&xyz.interface`打电话!
55+
56+
这种简化`File`显示三种类型之间的关系:
57+
58+
```zig
59+
pub const File = struct { pub fn writer(self: *File, buffer: []u8) Writer{ return .{ .file = self, .interface = std.Io.Writer{ .buffer = buffer, .vtable = .{.drain = Writer.drain}, } }; } pub const Writer = struct { file: *File, interface: std.Io.Writer, // this has a bunch of other fields fn drain(io_w: *std.Io.Writer, data: []const []const u8, splat: usize) !usize { const self: *Writer = @fieldParentPtr("interface", io_w); // .... } } }
60+
```
61+
62+
实例`File.Writer`需要存在于某个地方(例如在堆栈上),因为那是`std.Io.Writer`接口存在。有可能 那个`File`可以直接有一个`writer_interface: std.Io.Writer`字段,但这会限制你每个文件一个写入器,并且会膨胀`File`结构。
63+
64+
我们可以从上面看到,当我们调用`Writer`一个“界面”,它只是一个正常的结构。它有几个领域超越`buffer`和`vtable.drain`,但这些是唯一两个具有非默认值;我们必须提供它们。因`Writer`接口实现了很多典型的“ Writer ”行为,比如`writeAll`和`print`(用于格式化写作)。它还有多种方法,只有`Writer`实施可能会关心。例如,`File.Writer.drain`必须调用`consume`以便作者的内部状态可以更新。在文档中并排列出所有这些功能,起初让我感到困惑。希望这是文档生成有朝一日能够帮助解开的东西。
65+
66+
新`Writer`已经接管了多种方法。例如,`std.fmt.formatIntBuf`已经不存在了。替代者是`printInt`方法`Writer`. .但这需要一个`Writer`实例而不是简单`[]u8`以前要求。
67+
68+
很容易错过,但`Writer.fixed([]u8) Writer`函数是你正在寻找的。您将将此用于迁移到的任何函数`Writer`用来在A上工作`buffer: []u8`. .
69+
70+
迁移时,_可能会遇到以下错误:在“...”中没有名为“adaptToNewApi”的字段或成员函数。_你可以看到为什么发生这种情况,通过查看更新的实现`std.fmt.format`:
71+
72+
```zig
73+
pub fn format(writer: anytype, comptime fmt: []const u8, args: anytype) !void { var adapter = writer.adaptToNewApi(); return adapter.new_interface.print(fmt, args) catch |err| switch (err) { error.WriteFailed => return adapter.err.?, }; }
74+
```
75+
76+
因为这个功能被移动到`std.Io.Writer`, 任何`writer`传入`format`必须能够升级到新接口。这是完成的,同样,只是惯例,通过让“老” Writer 揭露一个`adaptToNewApi`返回一个类型的方法,它暴露了一个`new_interface: std.Io.Writer`领域。这是很容易实现使用基本`drain`实现,你可以在标准库中找到一些例子,但如果你不控制传统作者,那就没什么帮助了。
77+
78+
我犹豫是否要对这一变化发表意见。我不懂语言设计。然而,虽然我认为这是对当前API的改进,但我一直认为直接添加缓冲到`Writer`不是理想的。
79+
80+
我认为大多数语言都通过作文处理缓冲。你把一个 Reader / Writer ,并把它包装在缓冲阅读器或缓冲器。这种方法似乎既简单易懂,又易于实施,同时具有强大功能。它可以应用于缓冲和IO之外的东西。Zig似乎在与这种模式作斗争。而不是为此类问题提供一种有凝聚力和通用的方法,而是将一个特定API(IO)的特定特征(缓冲)融入到标准库中。也许我太密集了,无法理解,或者未来的变化会更全面地解决这个问题。

0 commit comments

Comments
 (0)