After working through the rest of 2017’s Advent of Code, I understand and appreciate @squeek502’s answer much better. The *Self
context is, indeed, the problem. To fix it, I need to make sure that buf_reader
has its final memory address before I create the reader. This means lazily initializing it in the ReadByLineIterator
struct.
There’s also no need for the @TypeOf
shenanigans. These types all have accessible names. After cleaning all that up and also taking the advice to stack-allocate the buffer, I’m left with something much more straightforward that doesn’t have the memory corruption problem (code below).
Given the simplified code, I’m curious about the assertion that this only “provides an illusion of convenience.” Now that it works, doesn’t it actually provide that convenience for the caller? Is there any downside to using this iterLines
function?
const std = @import("std");
const ReaderType = std.fs.File.Reader;
const BufReaderType = std.io.BufferedReader(4096, ReaderType);
const BufReaderReaderType = BufReaderType.Reader;
pub const ReadByLineIterator = struct {
file: std.fs.File,
reader: ReaderType,
buf_reader: BufReaderType,
stream: ?BufReaderReaderType,
buf: [4096]u8,
pub fn next(self: *@This()) !?[]u8 {
if (self.stream == null) {
self.stream = self.buf_reader.reader();
}
if (self.stream) |stream| {
return stream.readUntilDelimiterOrEof(&self.buf, '\n');
}
unreachable;
}
pub fn deinit(self: *@This()) void {
self.file.close();
}
};
// Iterate over the lines in the file using a buffered reader.
// Caller is responsible for calling deinit() on returned iterator when done.
pub fn iterLines(filename: []const u8) !ReadByLineIterator {
var file = try std.fs.cwd().openFile(filename, .{});
var reader = file.reader();
var buf_reader = std.io.bufferedReader(reader);
return ReadByLineIterator{
.file = file,
.reader = reader,
.buf_reader = buf_reader,
.stream = null,
.buf = undefined,
};
}