Skip to content

Commit

Permalink
std: add type-erased reader; base GenericReader on it
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
andrewrk committed Oct 1, 2023
1 parent af40bce commit 5405444
Show file tree
Hide file tree
Showing 4 changed files with 1,006 additions and 760 deletions.
243 changes: 240 additions & 3 deletions lib/std/io.zig
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const fs = std.fs;
const mem = std.mem;
const meta = std.meta;
const File = std.fs.File;
const Allocator = std.mem.Allocator;

pub const Mode = enum {
/// I/O operates normally, waiting for the operating system syscalls to complete.
Expand Down Expand Up @@ -105,7 +106,243 @@ pub fn getStdIn() File {
};
}

pub const Reader = @import("io/reader.zig").Reader;
pub fn GenericReader(
comptime Context: type,
comptime ReadError: type,
/// Returns the number of bytes read. It may be less than buffer.len.
/// If the number of bytes read is 0, it means end of stream.
/// End of stream is not an error condition.
comptime readFn: fn (context: Context, buffer: []u8) ReadError!usize,
) type {
return struct {
context: Context,

pub const Error = ReadError;
pub const NoEofError = ReadError || error{
EndOfStream,
};

pub inline fn read(self: Self, buffer: []u8) Error!usize {
return readFn(self.context, buffer);
}

pub inline fn readAll(self: Self, buffer: []u8) Error!usize {
return @errorCast(self.typeErased().readAll(buffer));
}

pub inline fn readAtLeast(self: Self, buffer: []u8, len: usize) Error!usize {
return @errorCast(self.typeErased().readAtLeast(buffer, len));
}

pub inline fn readNoEof(self: Self, buf: []u8) NoEofError!void {
return @errorCast(self.typeErased().readNoEof(buf));
}

pub inline fn readAllArrayList(
self: Self,
array_list: *std.ArrayList(u8),
max_append_size: usize,
) (error{StreamTooLong} || Error)!void {
return @errorCast(self.typeErased().readAllArrayList(array_list, max_append_size));
}

pub inline fn readAllArrayListAligned(
self: Self,
comptime alignment: ?u29,
array_list: *std.ArrayListAligned(u8, alignment),
max_append_size: usize,
) (error{StreamTooLong} || Error)!void {
return @errorCast(self.typeErased().readAllArrayListAligned(
alignment,
array_list,
max_append_size,
));
}

pub inline fn readAllAlloc(self: Self, allocator: Allocator, max_size: usize) Error![]u8 {
return @errorCast(self.typeErased().readAllAlloc(allocator, max_size));
}

pub inline fn readUntilDelimiterArrayList(
self: Self,
array_list: *std.ArrayList(u8),
delimiter: u8,
max_size: usize,
) Error!void {
return @errorCast(self.typeErased().readUntilDelimiterArrayList(
array_list,
delimiter,
max_size,
));
}

pub inline fn readUntilDelimiterAlloc(
self: Self,
allocator: Allocator,
delimiter: u8,
max_size: usize,
) Error![]u8 {
return @errorCast(self.typeErased().readUntilDelimiterAlloc(
allocator,
delimiter,
max_size,
));
}

pub inline fn readUntilDelimiter(self: Self, buf: []u8, delimiter: u8) Error![]u8 {
return @errorCast(self.typeErased().readUntilDelimiter(buf, delimiter));
}

pub inline fn readUntilDelimiterOrEofAlloc(
self: Self,
allocator: Allocator,
delimiter: u8,
max_size: usize,
) Error!?[]u8 {
return @errorCast(self.typeErased().readUntilDelimiterOrEofAlloc(
allocator,
delimiter,
max_size,
));
}

pub inline fn readUntilDelimiterOrEof(self: Self, buf: []u8, delimiter: u8) Error!?[]u8 {
return @errorCast(self.typeErased().readUntilDelimiterOrEof(buf, delimiter));
}

pub inline fn streamUntilDelimiter(
self: Self,
writer: anytype,
delimiter: u8,
optional_max_size: ?usize,
) (NoEofError || error{StreamTooLong} || @TypeOf(writer).Error)!void {
return @errorCast(self.typeErased().streamUntilDelimiter(
writer.typeErased(),
delimiter,
optional_max_size,
));
}

pub inline fn skipUntilDelimiterOrEof(self: Self, delimiter: u8) Error!void {
return @errorCast(self.typeErased().skipUntilDelimiterOrEof(delimiter));
}

pub inline fn readByte(self: Self) NoEofError!u8 {
return @errorCast(self.typeErased().readByte());
}

pub inline fn readByteSigned(self: Self) NoEofError!i8 {
return @errorCast(self.typeErased().readByteSigned());
}

pub inline fn readBytesNoEof(
self: Self,
comptime num_bytes: usize,
) NoEofError![num_bytes]u8 {
return @errorCast(self.typeErased().readBytesNoEof(num_bytes));
}

pub inline fn readIntoBoundedBytes(
self: Self,
comptime num_bytes: usize,
bounded: *std.BoundedArray(u8, num_bytes),
) Error!void {
return @errorCast(self.typeErased().readIntoBoundedBytes(num_bytes, bounded));
}

pub inline fn readBoundedBytes(
self: Self,
comptime num_bytes: usize,
) Error!std.BoundedArray(u8, num_bytes) {
return @errorCast(self.typeErased().readBoundedBytes(num_bytes));
}

pub inline fn readIntNative(self: Self, comptime T: type) NoEofError!T {
return @errorCast(self.typeErased().readIntNative(T));
}

pub inline fn readIntForeign(self: Self, comptime T: type) NoEofError!T {
return @errorCast(self.typeErased().readIntForeign(T));
}

pub inline fn readIntLittle(self: Self, comptime T: type) NoEofError!T {
return @errorCast(self.typeErased().readIntLittle(T));
}

pub inline fn readIntBig(self: Self, comptime T: type) NoEofError!T {
return @errorCast(self.typeErased().readIntBig(T));
}

pub inline fn readInt(self: Self, comptime T: type, endian: std.builtin.Endian) NoEofError!T {
return @errorCast(self.typeErased().readInt(T, endian));
}

pub inline fn readVarInt(
self: Self,
comptime ReturnType: type,
endian: std.builtin.Endian,
size: usize,
) NoEofError!ReturnType {
return @errorCast(self.typeErased().readVarInt(ReturnType, endian, size));
}

pub const SkipBytesOptions = TypeErasedReader.SkipBytesOptions;

pub inline fn skipBytes(
self: Self,
num_bytes: u64,
comptime options: SkipBytesOptions,
) Error!void {
return @errorCast(self.typeErased().skipBytes(num_bytes, options));
}

pub inline fn isBytes(self: Self, slice: []const u8) Error!bool {
return @errorCast(self.typeErased().isBytes(slice));
}

pub fn readStruct(self: Self, comptime T: type) Error!T {
return @errorCast(self.typeErased().readStruct(T));
}

pub inline fn readStructBig(self: Self, comptime T: type) Error!T {
return @errorCast(self.typeErased().readStructBig(T));
}

pub const ReadEnumError = Error || error{
/// An integer was read, but it did not match any of the tags in the supplied enum.
InvalidValue,
};

pub inline fn readEnum(
self: Self,
comptime Enum: type,
endian: std.builtin.Endian,
) ReadEnumError!Enum {
return @errorCast(self.typeErased().readEnum(Enum, endian));
}

pub inline fn typeErased(self: *const Self) TypeErasedReader {
return .{
.context = @ptrCast(&self.context),
.readFn = typeErasedReadFn,
};
}

const Self = @This();

fn typeErasedReadFn(context: *const anyopaque, buffer: []u8) anyerror!usize {
const ptr: *const Context = @alignCast(@ptrCast(context));
return readFn(ptr.*, buffer);
}
};
}

/// Deprecated; consider switching to `TypeErasedReader` or use `GenericReader`
/// to use previous API.
pub const Reader = GenericReader;

pub const TypeErasedReader = @import("io/Reader.zig");

pub const Writer = @import("io/writer.zig").Writer;
pub const SeekableStream = @import("io/seekable_stream.zig").SeekableStream;

Expand Down Expand Up @@ -168,7 +405,7 @@ test "null_writer" {
}

pub fn poll(
allocator: std.mem.Allocator,
allocator: Allocator,
comptime StreamEnum: type,
files: PollFiles(StreamEnum),
) Poller(StreamEnum) {
Expand Down Expand Up @@ -418,6 +655,7 @@ pub fn PollFiles(comptime StreamEnum: type) type {
}

test {
_ = TypeErasedReader;
_ = @import("io/bit_reader.zig");
_ = @import("io/bit_writer.zig");
_ = @import("io/buffered_atomic_file.zig");
Expand All @@ -427,7 +665,6 @@ test {
_ = @import("io/counting_writer.zig");
_ = @import("io/counting_reader.zig");
_ = @import("io/fixed_buffer_stream.zig");
_ = @import("io/reader.zig");
_ = @import("io/writer.zig");
_ = @import("io/peek_stream.zig");
_ = @import("io/seekable_stream.zig");
Expand Down
Loading

0 comments on commit 5405444

Please sign in to comment.