Zig中修复readByLine文件行迭代器函数并支持BufferedReader
问题
我想在Zig中实现一个返回行迭代器的辅助函数,找到了2021年的一段代码,但在Zig 0.11.0编译时出现错误。
原代码如下:
const std = @import("std"); fn ReturnTypeOf(comptime callable_type: type) type { const info = @typeInfo(callable_type); return switch (info) { .Fn => info.Fn.return_type.?, .BoundFn => info.BoundFn.return_type.?, else => @compileError("unsupported type " ++ @typeName(callable_type)), }; } fn ReadByLineIterator(comptime ReaderType: type) type { return struct { // Should be customizable! But also if you have lines of more than 64k you have other problems. pub const MaxBufferSize: usize = 64 * 1024; allocator: *std.mem.Allocator, reader: ReaderType, last_read: ?[]const u8, pub fn deinit(self: @This()) void { if (self.last_read) |buf| self.allocator.free(buf); } pub fn next(self: *@This()) !?[]const u8 { if (self.last_read) |buf| { self.allocator.free(buf); self.last_read = null; } const line = try self.reader.readUntilDelimiterOrEofAlloc(self.allocator, '\n', MaxBufferSize); self.last_read = line; return line; } }; } fn readByLine(allocator: *std.mem.Allocator, file: anytype) ReadByLineIterator(ReturnTypeOf(@TypeOf(file.reader))) { return .{ .allocator = allocator, .reader = file.reader(), .last_read = null, }; } pub fn main() !void { var it = readByLine(std.heap.page_allocator, std.io.getStdIn()); defer it.deinit(); while (try it.next()) |buf| { std.debug.print("read line: \"{s}\"\n", .{buf}); } }
编译报错信息:
src/util.zig:52:110: error: no field named 'reader' in struct 'fs.file.File' pub fn readByLine(allocator: *std.mem.Allocator, file: anytype) ReadByLineIterator(ReturnTypeOf(@TypeOf(file.reader))) { ^~~~~~ /opt/homebrew/Cellar/zig/0.11.0/lib/zig/std/fs/file.zig:13:18: note: struct declared here pub const File = struct { ^~~~~~
明明File结构体包含reader方法:
pub const Reader = io.Reader(File, ReadError, read); pub fn reader(file: File) Reader { return .{ .context = file }; }
需要修复readByLine函数,使其同时支持File和BufferedReader。
解决方案
问题核心是@TypeOf(file.reader)的用法错误:reader是结构体的方法而非字段,file.reader会被编译器解析为访问字段,而非获取方法类型。以下是两种修复方式:
方式1:直接推导reader方法的返回类型
简化返回类型的推导逻辑,通过@TypeOf(file.reader())直接获取方法的返回类型(comptime阶段仅做类型推导,不会实际执行方法调用):
fn readByLine(allocator: std.mem.Allocator, file: anytype) ReadByLineIterator(@TypeOf(file.reader())) { return .{ .allocator = allocator, .reader = file.reader(), .last_read = null, }; }
同时注意:Zig 0.11+版本中*std.mem.Allocator已改为std.mem.Allocator,需同步更新参数类型。
方式2:修正ReturnTypeOf的调用逻辑
如果想保留ReturnTypeOf辅助函数,需要传入方法的函数类型而非尝试访问字段:
fn readByLine(allocator: std.mem.Allocator, file: anytype) ReadByLineIterator(ReturnTypeOf(@TypeOf(&file.reader))) { return .{ .allocator = allocator, .reader = file.reader(), .last_read = null, }; }
这里&file.reader用于获取方法的函数类型,而非尝试访问不存在的字段。
最终可运行代码
推荐使用方式1的简洁写法,完整代码如下:
const std = @import("std"); fn ReadByLineIterator(comptime ReaderType: type) type { return struct { pub const MaxBufferSize: usize = 64 * 1024; allocator: std.mem.Allocator, reader: ReaderType, last_read: ?[]const u8, pub fn deinit(self: @This()) void { if (self.last_read) |buf| self.allocator.free(buf); } pub fn next(self: *@This()) !?[]const u8 { if (self.last_read) |buf| { self.allocator.free(buf); self.last_read = null; } const line = try self.reader.readUntilDelimiterOrEofAlloc(self.allocator, '\n', MaxBufferSize); self.last_read = line; return line; } }; } fn readByLine(allocator: std.mem.Allocator, file: anytype) ReadByLineIterator(@TypeOf(file.reader())) { return .{ .allocator = allocator, .reader = file.reader(), .last_read = null, }; } pub fn main() !void { var it = readByLine(std.heap.page_allocator, std.io.getStdIn()); defer it.deinit(); while (try it.next()) |buf| { std.debug.print("read line: \"{s}\"\n", .{buf}); } }
支持BufferedReader的示例
上述代码天然支持BufferedReader,示例如下:
pub fn main() !void { var stdin = std.io.getStdIn(); var buf_reader = std.io.bufferedReader(stdin.reader()); var it = readByLine(std.heap.page_allocator, buf_reader); defer it.deinit(); while (try it.next()) |buf| { std.debug.print("read line: \"{s}\"\n", .{buf}); } }
内容的提问来源于stack exchange,提问作者danvk
相关产品推荐
相关产品推荐

