我正在尝试生成一个结构体来保存对 stdin/stdout/stderr 的每个读者和作者的引用
然而,我一直试图声明适当的类型。我最简单的代码:
const std = @import("std");
const IoTrio = struct {
in: std.io.GenericReader,
out: std.io.GenericWriter,
err: std.io.GenericWriter,
};
pub fn giveIo() !IoTrio {
const stdout = std.io.getStdOut().writer();
var stdin_bo = std.io.bufferedReader(std.io.getStdIn().reader());
const stdin = stdin_bo.reader();
const stderr = std.debug;
@compileLog(@TypeOf(stdin ));
@compileLog(@TypeOf(stdout));
@compileLog(@TypeOf(stderr));
return IoTrio{
.in = stdin,
.out = stdout,
.err = std.debug,
};
}
pub fn main() !void {
_ = giveIo();
}
结构编译失败:
Produces error:
types.zig:5:15: error: expected type 'type', found 'fn (comptime type, comptime type, comptime anytype) type'
in: std.io.GenericReader,
~~~~~~^~~~~~~~~~~~~~
我查看了这个答案,但不幸的是,这并没有真正帮助 - 正如答案作者所指出的,输出很复杂,并且不容易理解 - 讨论到此结束。
确实,输出相当混乱:
@as(type, io.GenericReader(*io.buffered_reader.BufferedReader(4096,io.GenericReader(fs.File,error{AccessDenied,Unexpected,InputOutput,BrokenPipe,SystemResources,OperationAborted,WouldBlock,ConnectionResetByPeer,IsDir,ConnectionTimedOut,NotOpenForReading,SocketNotConnected},(function 'read'))),error{AccessDenied,Unexpected,InputOutput,BrokenPipe,SystemResources,OperationAborted,WouldBlock,ConnectionResetByPeer,IsDir,ConnectionTimedOut,NotOpenForReading,SocketNotConnected},(function 'read')))
@as(type, io.GenericWriter(fs.File,error{AccessDenied,Unexpected,DiskQuota,FileTooBig,InputOutput,NoSpaceLeft,DeviceBusy,InvalidArgument,BrokenPipe,SystemResources,OperationAborted,NotOpenForWriting,LockViolation,WouldBlock,ConnectionResetByPeer},(function 'write')))
@as(type, type)
似乎暗示着每个都是一个函数?但我无法将这些值插入到我的结构声明中,我也不明白这是什么意思让我猜测更合理的东西。
如何读取这些输出,以导出结构所需的类型符号?
将读者和作者存储在结构中很困难。
第一个问题是函数
GenericWriter
是一个不幸的名字。它是从写入函数和写入函数实现的一些上下文构造通用编写器的函数。
请注意,即使您的代码已编译,您也有释放后使用的情况。缓冲读取器在堆栈上创建一个缓冲区,但该堆栈在函数退出时失效,从而为您留下来自无效内存的读取器。
这个缓冲区应该放在哪里?它不能位于您的结构内部,因此所有通用读取器都必须有足够的空间来存储程序中使用的最大缓冲区。它也不能存在于堆上,因为 Zig 不会隐式分配。然后您还必须释放它,但您没有。
如果您不想依赖特定的读取器/写入器实现,则可以轻松地在 main 中创建所需的所有读取器和写入器,然后将它们作为“writer: anytype”参数传递给其他函数,而不是传递它们像你在这里一样。
如果您确实需要将它们存储在结构中,可以使用
AnyWriter
和 AnyReader
。但正确使用它们很困难,因为您必须自己管理它们的状态。以下是如何做到这一点的示例:
const std = @import("std");
const IoTrio = struct {
in: std.io.AnyReader,
out: std.io.AnyWriter,
err: std.io.AnyWriter,
// here you store the implementation-specific data
state: union(enum) {
std: struct {
// for example, the buffered stdin reader looks like this
stdin_buffer: *std.io.BufferedReader(4096, std.fs.File.Reader),
},
something_else: struct {
// something
},
},
fn fromSomethingElse() IoTrio {
return .{};
}
fn fromStd(a: std.mem.Allocator) !IoTrio {
// stdout and stderr are easy, they don't need to be cleaned up
const stdout = std.io.getStdOut().writer();
const stderr = std.io.getStdErr().writer();
// buffered stdin is harder. You have to rememeber that you allocated
// the buffer and later free the buffer.
const buffer_impl = std.io.bufferedReader(std.io.getStdIn().reader());
const buffer_impl_on_heap = try a.create(@TypeOf(buffer_impl));
buffer_impl_on_heap.* = buffer_impl;
const stdin = buffer_impl_on_heap.reader();
return IoTrio{
.in = stdin.any(),
.out = stdout.any(),
.err = stderr.any(),
.state = .{ .std = .{ .stdin_buffer = buffer_impl_on_heap } },
};
}
fn deinit(self: *const IoTrio, a: std.mem.Allocator) void {
switch (self.state) {
.std => |s| {
a.destroy(s.stdin_buffer);
},
.something_else => {
// something
},
}
}
};
pub fn main() !void {
const a = std.heap.page_allocator; // slideware, get a better allocator!
const io = try IoTrio.fromStd(a);
defer io.deinit(a);
try io.out.print("Hello {s}!", .{"world"});
}
此代码创建读取器和写入器,在堆上分配它们的状态,并在调用 deinit 时清理它们。如果您在堆栈上创建它们并将它们的生命周期绑定到某个本地范围,那么所有这一切都会自动发生:
const std = @import("std");
pub fn main() !void {
const out = std.io.getStdOut().writer();
const err = std.io.getStdErr().writer();
var in_impl = std.io.bufferedReader(std.io.getStdIn().reader());
const in = in_impl.reader();
try takeIo(in, out, err);
}
fn takeIo(in: anytype, out: anytype, err: anytype) !void {
_ = in;
_ = err;
try out.print("Hello {s}!\n", .{"world"});
}