From f1fa66771c5dc5e6779729db66e674ba9ec063e2 Mon Sep 17 00:00:00 2001 From: torque Date: Sun, 5 Nov 2023 12:10:02 -0800 Subject: [PATCH] std.io: add AnyWriter This is a non-generic writer that holds a type-erased pointer to a generic writer and is suitable for use in scenarios, such as runtime callbacks, where generic functions cannot be used. The approach taken here is a bit different from AnyReader in that the type-erased version has not replaced the generic version. Instead, an interface mixin is used to share the same interface between the two implementations. --- lib/std/io.zig | 3 +++ lib/std/io/test.zig | 9 +++++++++ lib/std/io/writer.zig | 38 ++++++++++++++++++++++++++++++++++++-- 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/lib/std/io.zig b/lib/std/io.zig index e19b2e0e163c..3c0aee4b3060 100644 --- a/lib/std/io.zig +++ b/lib/std/io.zig @@ -340,6 +340,9 @@ pub const Reader = GenericReader; pub const AnyReader = @import("io/Reader.zig"); pub const Writer = @import("io/writer.zig").Writer; +pub const AnyWriter = @import("io/writer.zig").AnyWriter; +pub const WriterInterface = @import("io/writer.zig").WriterInterface; + pub const SeekableStream = @import("io/seekable_stream.zig").SeekableStream; pub const BufferedWriter = @import("io/buffered_writer.zig").BufferedWriter; diff --git a/lib/std/io/test.zig b/lib/std/io/test.zig index 6de0fe1e5387..0263d105e2f2 100644 --- a/lib/std/io/test.zig +++ b/lib/std/io/test.zig @@ -190,3 +190,12 @@ test "GenericReader methods can return error.EndOfStream" { fbs.reader().isBytes("foo"), ); } + +test "AnyWriter write to buffer" { + var buffer = std.ArrayList(u8).init(std.testing.allocator); + defer buffer.deinit(); + const writer = buffer.writer().any(); + + try writer.print("{s}", .{"greetings"}); + try std.testing.expectEqualStrings("greetings", buffer.items); +} diff --git a/lib/std/io/writer.zig b/lib/std/io/writer.zig index f1c0efda9037..64d4892671cd 100644 --- a/lib/std/io/writer.zig +++ b/lib/std/io/writer.zig @@ -11,12 +11,46 @@ pub fn Writer( context: Context, const Self = @This(); - pub const Error = WriteError; - pub fn write(self: Self, bytes: []const u8) Error!usize { + pub fn write(self: Self, bytes: []const u8) Self.Error!usize { return writeFn(self.context, bytes); } + pub inline fn any(self: *const Self) AnyWriter { + return .{ + .context = @ptrCast(self), + .writeFn = typeErasedWriteFn, + }; + } + + fn typeErasedWriteFn(context: *const anyopaque, bytes: []const u8) anyerror!usize { + const self: *const Self = @ptrCast(@alignCast(context)); + return self.write(bytes); + } + + pub usingnamespace WriterInterface(Self, WriteError); + }; +} + +pub const AnyWriter = struct { + context: *const anyopaque, + writeFn: *const fn (context: *const anyopaque, bytes: []const u8) AnyWriter.Error!usize, + + pub fn write(self: AnyWriter, bytes: []const u8) AnyWriter.Error!usize { + return self.writeFn(self.context, bytes); + } + + pub inline fn any(self: AnyWriter) AnyWriter { + return self; + } + + pub usingnamespace WriterInterface(AnyWriter, anyerror); +}; + +pub fn WriterInterface(comptime Self: type, comptime ErrSet: type) type { + return struct { + pub const Error = ErrSet; + pub fn writeAll(self: Self, bytes: []const u8) Error!void { var index: usize = 0; while (index != bytes.len) {