Skip to content

Commit 01ccc73

Browse files
committed
std: add type-erased reader; base GenericReader on it
The idea here is to avoid code bloat by having only one actual io.Reader implementation, which is type erased, and then implement a GenericReader that preserves type information on top of that as thin glue code. The strategy here is for that glue code to `@errSetCast` the result of the type-erased reader functions, however, while trying to do that I ran into #17343.
1 parent 937e8cb commit 01ccc73

File tree

4 files changed

+998
-760
lines changed

4 files changed

+998
-760
lines changed

lib/std/io.zig

+237-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const fs = std.fs;
1010
const mem = std.mem;
1111
const meta = std.meta;
1212
const File = std.fs.File;
13+
const Allocator = std.mem.Allocator;
1314

1415
pub const Mode = enum {
1516
/// I/O operates normally, waiting for the operating system syscalls to complete.
@@ -105,7 +106,240 @@ pub fn getStdIn() File {
105106
};
106107
}
107108

108-
pub const Reader = @import("io/reader.zig").Reader;
109+
pub fn GenericReader(
110+
comptime Context: type,
111+
comptime ReadError: type,
112+
/// Returns the number of bytes read. It may be less than buffer.len.
113+
/// If the number of bytes read is 0, it means end of stream.
114+
/// End of stream is not an error condition.
115+
comptime readFn: fn (context: Context, buffer: []u8) ReadError!usize,
116+
) type {
117+
return struct {
118+
context: Context,
119+
120+
pub const Error = ReadError;
121+
122+
pub inline fn read(self: Self, buffer: []u8) Error!usize {
123+
return readFn(self.context, buffer);
124+
}
125+
126+
pub inline fn readAll(self: Self, buffer: []u8) Error!usize {
127+
return @errSetCast(self.typeErased().readAll(buffer));
128+
}
129+
130+
pub inline fn readAtLeast(self: Self, buffer: []u8, len: usize) Error!usize {
131+
return @errSetCast(self.typeErased().readAtLeast(buffer, len));
132+
}
133+
134+
pub inline fn readNoEof(self: Self, buf: []u8) (Error || error{EndOfStream})!void {
135+
return @errSetCast(self.typeErased().readNoEof(buf));
136+
}
137+
138+
pub inline fn readAllArrayList(
139+
self: Self,
140+
array_list: *std.ArrayList(u8),
141+
max_append_size: usize,
142+
) (error{StreamTooLong} || Error)!void {
143+
return @errSetCast(self.typeErased().readAllArrayList(array_list, max_append_size));
144+
}
145+
146+
pub inline fn readAllArrayListAligned(
147+
self: Self,
148+
comptime alignment: ?u29,
149+
array_list: *std.ArrayListAligned(u8, alignment),
150+
max_append_size: usize,
151+
) (error{StreamTooLong} || Error)!void {
152+
return @errSetCast(self.typeErased().readAllArrayListAligned(
153+
alignment,
154+
array_list,
155+
max_append_size,
156+
));
157+
}
158+
159+
pub inline fn readAllAlloc(self: Self, allocator: Allocator, max_size: usize) Error![]u8 {
160+
return @errSetCast(self.typeErased().readAllAlloc(allocator, max_size));
161+
}
162+
163+
pub inline fn readUntilDelimiterArrayList(
164+
self: Self,
165+
array_list: *std.ArrayList(u8),
166+
delimiter: u8,
167+
max_size: usize,
168+
) Error!void {
169+
return @errSetCast(self.typeErased().readUntilDelimiterArrayList(
170+
array_list,
171+
delimiter,
172+
max_size,
173+
));
174+
}
175+
176+
pub inline fn readUntilDelimiterAlloc(
177+
self: Self,
178+
allocator: Allocator,
179+
delimiter: u8,
180+
max_size: usize,
181+
) Error![]u8 {
182+
return @errSetCast(self.typeErased().readUntilDelimiterAlloc(
183+
allocator,
184+
delimiter,
185+
max_size,
186+
));
187+
}
188+
189+
pub inline fn readUntilDelimiter(self: Self, buf: []u8, delimiter: u8) Error![]u8 {
190+
return @errSetCast(self.typeErased().readUntilDelimiter(buf, delimiter));
191+
}
192+
193+
pub inline fn readUntilDelimiterOrEofAlloc(
194+
self: Self,
195+
allocator: Allocator,
196+
delimiter: u8,
197+
max_size: usize,
198+
) Error!?[]u8 {
199+
return @errSetCast(self.typeErased().readUntilDelimiterOrEofAlloc(
200+
allocator,
201+
delimiter,
202+
max_size,
203+
));
204+
}
205+
206+
pub inline fn readUntilDelimiterOrEof(self: Self, buf: []u8, delimiter: u8) Error!?[]u8 {
207+
return @errSetCast(self.typeErased().readUntilDelimiterOrEof(buf, delimiter));
208+
}
209+
210+
pub inline fn streamUntilDelimiter(
211+
self: Self,
212+
writer: anytype,
213+
delimiter: u8,
214+
optional_max_size: ?usize,
215+
) (Error || error{ EndOfStream, StreamTooLong } || @TypeOf(writer).Error)!void {
216+
return @errSetCast(self.typeErased().streamUntilDelimiter(
217+
writer.typeErased(),
218+
delimiter,
219+
optional_max_size,
220+
));
221+
}
222+
223+
pub inline fn skipUntilDelimiterOrEof(self: Self, delimiter: u8) Error!void {
224+
return @errSetCast(self.typeErased().skipUntilDelimiterOrEof(delimiter));
225+
}
226+
227+
pub inline fn readByte(self: Self) (Error || error{EndOfStream})!u8 {
228+
return @errSetCast(self.typeErased().readByte());
229+
}
230+
231+
pub inline fn readByteSigned(self: Self) (Error || error{EndOfStream})!i8 {
232+
return @errSetCast(self.typeErased().readByteSigned());
233+
}
234+
235+
pub inline fn readBytesNoEof(
236+
self: Self,
237+
comptime num_bytes: usize,
238+
) (Error || error{EndOfStream})![num_bytes]u8 {
239+
return @errSetCast(self.typeErased().readBytesNoEof(num_bytes));
240+
}
241+
242+
pub inline fn readIntoBoundedBytes(
243+
self: Self,
244+
comptime num_bytes: usize,
245+
bounded: *std.BoundedArray(u8, num_bytes),
246+
) Error!void {
247+
return @errSetCast(self.typeErased().readIntoBoundedBytes(num_bytes, bounded));
248+
}
249+
250+
pub inline fn readBoundedBytes(
251+
self: Self,
252+
comptime num_bytes: usize,
253+
) Error!std.BoundedArray(u8, num_bytes) {
254+
return @errSetCast(self.typeErased().readBoundedBytes(num_bytes));
255+
}
256+
257+
pub inline fn readIntNative(self: Self, comptime T: type) (Error || error{EndOfStream})!T {
258+
return @errSetCast(self.typeErased().readIntNative(T));
259+
}
260+
261+
pub inline fn readIntForeign(self: Self, comptime T: type) (Error || error{EndOfStream})!T {
262+
return @errSetCast(self.typeErased().readIntForeign(T));
263+
}
264+
265+
pub inline fn readIntLittle(self: Self, comptime T: type) Error!T {
266+
return @errSetCast(self.typeErased().readIntLittle(T));
267+
}
268+
269+
pub inline fn readIntBig(self: Self, comptime T: type) Error!T {
270+
return @errSetCast(self.typeErased().readIntBig(T));
271+
}
272+
273+
pub inline fn readInt(self: Self, comptime T: type, endian: std.builtin.Endian) Error!T {
274+
return @errSetCast(self.typeErased().readInt(T, endian));
275+
}
276+
277+
pub inline fn readVarInt(
278+
self: Self,
279+
comptime ReturnType: type,
280+
endian: std.builtin.Endian,
281+
size: usize,
282+
) !ReturnType {
283+
return @errSetCast(self.typeErased().readVarInt(ReturnType, endian, size));
284+
}
285+
286+
pub const SkipBytesOptions = TypeErasedReader.SkipBytesOptions;
287+
288+
pub inline fn skipBytes(
289+
self: Self,
290+
num_bytes: u64,
291+
comptime options: SkipBytesOptions,
292+
) Error!void {
293+
return @errSetCast(self.typeErased().skipBytes(num_bytes, options));
294+
}
295+
296+
pub inline fn isBytes(self: Self, slice: []const u8) Error!bool {
297+
return @errSetCast(self.typeErased().isBytes(slice));
298+
}
299+
300+
pub fn readStruct(self: Self, comptime T: type) Error!T {
301+
return @errSetCast(self.typeErased().readStruct(T));
302+
}
303+
304+
pub inline fn readStructBig(self: Self, comptime T: type) Error!T {
305+
return @errSetCast(self.typeErased().readStructBig(T));
306+
}
307+
308+
pub const ReadEnumError = Error || error{
309+
/// An integer was read, but it did not match any of the tags in the supplied enum.
310+
InvalidValue,
311+
};
312+
313+
pub inline fn readEnum(
314+
self: Self,
315+
comptime Enum: type,
316+
endian: std.builtin.Endian,
317+
) ReadEnumError!Enum {
318+
return @errSetCast(self.typeErased().readEnum(Enum, endian));
319+
}
320+
321+
pub inline fn typeErased(self: *const Self) TypeErasedReader {
322+
return .{
323+
.context = @ptrCast(&self.context),
324+
.readFn = typeErasedReadFn,
325+
};
326+
}
327+
328+
const Self = @This();
329+
330+
fn typeErasedReadFn(context: *const anyopaque, buffer: []u8) anyerror!usize {
331+
const ptr: *const Context = @alignCast(@ptrCast(context));
332+
return readFn(ptr.*, buffer);
333+
}
334+
};
335+
}
336+
337+
/// Deprecated; consider switching to `TypeErasedReader` or use `GenericReader`
338+
/// to use previous API.
339+
pub const Reader = GenericReader;
340+
341+
pub const TypeErasedReader = @import("io/Reader.zig");
342+
109343
pub const Writer = @import("io/writer.zig").Writer;
110344
pub const SeekableStream = @import("io/seekable_stream.zig").SeekableStream;
111345

@@ -168,7 +402,7 @@ test "null_writer" {
168402
}
169403

170404
pub fn poll(
171-
allocator: std.mem.Allocator,
405+
allocator: Allocator,
172406
comptime StreamEnum: type,
173407
files: PollFiles(StreamEnum),
174408
) Poller(StreamEnum) {
@@ -418,6 +652,7 @@ pub fn PollFiles(comptime StreamEnum: type) type {
418652
}
419653

420654
test {
655+
_ = TypeErasedReader;
421656
_ = @import("io/bit_reader.zig");
422657
_ = @import("io/bit_writer.zig");
423658
_ = @import("io/buffered_atomic_file.zig");
@@ -427,7 +662,6 @@ test {
427662
_ = @import("io/counting_writer.zig");
428663
_ = @import("io/counting_reader.zig");
429664
_ = @import("io/fixed_buffer_stream.zig");
430-
_ = @import("io/reader.zig");
431665
_ = @import("io/writer.zig");
432666
_ = @import("io/peek_stream.zig");
433667
_ = @import("io/seekable_stream.zig");

0 commit comments

Comments
 (0)