Hi! New Zig user here.
I want to wrap a File.Reader in a struct together with some other functionality, and want to make use of it through the Io.Reader interface. For MVE's sake the following code suffices (it reads its own source file):
const std = @import("std");
const expect = std.testing.expect;
const SelfReader = struct {
reader: std.fs.File.Reader = undefined,
buffer: [1024]u8 = undefined,
pub fn init() !SelfReader {
const path = @src().file;
const file = try std.fs.cwd().openFile(path, .{ .mode = .read_only });
var sr = SelfReader{};
sr.reader = file.reader(&sr.buffer);
return sr;
}
pub fn getInterface(self: SelfReader) *std.Io.Reader {
return @constCast(&self.reader.interface);
}
pub fn close(self: SelfReader) void {
self.reader.file.close();
}
};
fn takeByte(iface: *std.Io.Reader) !u8 {
return iface.takeByte();
}
test "get interface" {
var sr = try SelfReader.init();
defer sr.close();
const reader = &sr.reader.interface;
try expect(try reader.takeByte() == 'c');
try expect(try takeByte(reader) == 'o');
}
So far so good. The above test works as expected and I can pass the *Io.Reader to another function (which I want to do in my full code).
However, I would like the 'get interface' step to also be part of the struct, hence the getInterface function. Even though I would expect this to work the same, using the *Io.Reader returned by getInterface results in an error.
test "get interface through function" {
var sr = try SelfReader.init();
defer sr.close();
const reader = sr.getInterface();
try expect(reader.takeByte() == std.Io.Reader.Error.ReadFailed);
// Subsequent reads give random data, I assume undefined contents of the buffer
}
I don't understand why this fails, also after reading through some documentation and blog posts. The closest I have come to an answer is that somehow part of the underlying File.Reader goes out of scope, but I don't see where or how that should happen here as I am (I assume) correctly passing around a pointer to the Io.Reader. When using this pointer as a function argument like in the first test it works, so why should it fail here?
After messing around with the code a bit, I found the following 'workaround' which might point to an actual answer but to me makes it only more confusing. By first accessing the Io.Reader directly, the pointer returned by getInterface suddenly does work!
test "get interface through function workaround" {
var sr = try SelfReader.init();
defer sr.close();
try sr.reader.interface.fill(1);
const reader = sr.getInterface();
try expect(try reader.takeByte() == 'c');
try expect(try takeByte(reader) == 'o');
}
My best guess at this point is that somehow using the pointer from getInterface doesn't trigger the fill method, but once the buffer is filled it is possible to read its contents. Having only started with Zig last week this is where I'm starting to get lost though. What is going on here?
I've done most testing with Zig 0.15.2 on Fedora Linux. I've also tried the current 0.16.0-dev.1441+2ea55d715 with a DebugAllocator and Io.Threaded and the result is pretty much the same, except that instead of a ReadFailed error I get a panic: programmer bug caused syscall error: INVAL. Attached is the full code for 0.15.2 and 0.16.0-dev.