Skip to content

Commit 5405444

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 af40bce commit 5405444

File tree

4 files changed

+1006
-760
lines changed

4 files changed

+1006
-760
lines changed

lib/std/io.zig

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

@@ -168,7 +405,7 @@ test "null_writer" {
168405
}
169406

170407
pub fn poll(
171-
allocator: std.mem.Allocator,
408+
allocator: Allocator,
172409
comptime StreamEnum: type,
173410
files: PollFiles(StreamEnum),
174411
) Poller(StreamEnum) {
@@ -418,6 +655,7 @@ pub fn PollFiles(comptime StreamEnum: type) type {
418655
}
419656

420657
test {
658+
_ = TypeErasedReader;
421659
_ = @import("io/bit_reader.zig");
422660
_ = @import("io/bit_writer.zig");
423661
_ = @import("io/buffered_atomic_file.zig");
@@ -427,7 +665,6 @@ test {
427665
_ = @import("io/counting_writer.zig");
428666
_ = @import("io/counting_reader.zig");
429667
_ = @import("io/fixed_buffer_stream.zig");
430-
_ = @import("io/reader.zig");
431668
_ = @import("io/writer.zig");
432669
_ = @import("io/peek_stream.zig");
433670
_ = @import("io/seekable_stream.zig");

0 commit comments

Comments
 (0)