Skip to content

Commit a6e915c

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 4df7f7c commit a6e915c

File tree

4 files changed

+1018
-760
lines changed

4 files changed

+1018
-760
lines changed

lib/std/io.zig

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

@@ -168,7 +417,7 @@ test "null_writer" {
168417
}
169418

170419
pub fn poll(
171-
allocator: std.mem.Allocator,
420+
allocator: Allocator,
172421
comptime StreamEnum: type,
173422
files: PollFiles(StreamEnum),
174423
) Poller(StreamEnum) {
@@ -418,6 +667,7 @@ pub fn PollFiles(comptime StreamEnum: type) type {
418667
}
419668

420669
test {
670+
_ = TypeErasedReader;
421671
_ = @import("io/bit_reader.zig");
422672
_ = @import("io/bit_writer.zig");
423673
_ = @import("io/buffered_atomic_file.zig");
@@ -427,7 +677,6 @@ test {
427677
_ = @import("io/counting_writer.zig");
428678
_ = @import("io/counting_reader.zig");
429679
_ = @import("io/fixed_buffer_stream.zig");
430-
_ = @import("io/reader.zig");
431680
_ = @import("io/writer.zig");
432681
_ = @import("io/peek_stream.zig");
433682
_ = @import("io/seekable_stream.zig");

0 commit comments

Comments
 (0)