Zig version: 0.12.0-dev.800+a9b37ac63
Just stumbled upon the new type erased std.io.AnyReader and wanted to use it to write generic code. But I quickly wrote some buggy code, as shown in this example:
// test.zig
const std = @import("std");
const fs = std.fs;
const io = std.io;
const CoolReader = struct {
file: fs.File,
fn init() !CoolReader {
return CoolReader{ .file = try fs.cwd().openFile("test.zig", .{}) };
}
fn reader(self: *CoolReader) io.AnyReader {
return self.file.reader().any();
}
};
pub fn main() !void {
var rd = try CoolReader.init();
_ = try rd.reader().readInt(u32, .Little);
}
Running this with zig run test.zig caused some undefined behaviour, as the program reads the standard input instead of it's own sourcefile.
The problem is that the reader returned from self.file.reader() is discarded at the end of the function, but the returned io.AnyReader contains a pointer to it:
|
pub inline fn any(self: *const Self) AnyReader { |
|
return .{ |
|
.context = @ptrCast(&self.context), |
|
.readFn = typeErasedReadFn, |
|
}; |
|
} |
As the reader() function copies the File struct into the context of the returned GenericReader, I instead stored this reader and accessed the context variable, if I needed the File struct.
But this pattern didn't work as I introduced a io.CountingReader, as it uses a pointer context for it's reader to update the struct values:
|
pub const Reader = io.Reader(*@This(), Error, read); |
This meant that I have to store the io.CountingReader for the actual counter, but also the io.CountingReader.Reader struct to be able to return an io.AnyReader without undefined behaviour. But theoretical we could copy the context pointer instead of a double indirection.
TLDR: It's just complicated to use AnyReader, as you need to store the underlying reader. But without knowing the implementation details, you also need to store the actual struct the reader is based upon. This makes it hard to use and can cause unexpected undefined behaviour.
Possible solutions:
- Add an
anyReader function, that directly returns an AnyReader and remove the any() function of the GenericReader
- Good compability with existing code
- More code necessary to support
reader() and anyReader()
- Always use a pointer context in
GenericReader
any() now just copies the pointer into AnyReader
- It's clear, that any reader just contains a reference to the actual struct.
- May break some existing code, that assumes that
reader() creates a copy
Zig version: 0.12.0-dev.800+a9b37ac63
Just stumbled upon the new type erased
std.io.AnyReaderand wanted to use it to write generic code. But I quickly wrote some buggy code, as shown in this example:Running this with
zig run test.zigcaused some undefined behaviour, as the program reads the standard input instead of it's own sourcefile.The problem is that the reader returned from
self.file.reader()is discarded at the end of the function, but the returnedio.AnyReadercontains a pointer to it:zig/lib/std/io.zig
Lines 336 to 341 in 75b48ef
As the
reader()function copies the File struct into the context of the returnedGenericReader, I instead stored this reader and accessed thecontextvariable, if I needed the File struct.But this pattern didn't work as I introduced a
io.CountingReader, as it uses a pointer context for it's reader to update the struct values:zig/lib/std/io/counting_reader.zig
Line 12 in 75b48ef
This meant that I have to store the
io.CountingReaderfor the actual counter, but also theio.CountingReader.Readerstruct to be able to return anio.AnyReaderwithout undefined behaviour. But theoretical we could copy the context pointer instead of a double indirection.TLDR: It's just complicated to use
AnyReader, as you need to store the underlying reader. But without knowing the implementation details, you also need to store the actual struct the reader is based upon. This makes it hard to use and can cause unexpected undefined behaviour.Possible solutions:
anyReaderfunction, that directly returns anAnyReaderand remove theany()function of theGenericReaderreader()andanyReader()GenericReaderany()now just copies the pointer intoAnyReaderreader()creates a copy