    const std = @import("../std.zig");
    const assert = std.debug.assert;
    const mem = std.mem;

    pub fn Writer(
        comptime Context: type,
        comptime WriteError: type,
        comptime writeFn: fn (context: Context, bytes: []const u8) WriteError!usize,
    ) type {
        return struct {
            context: Context,

            const Self = @This();
            pub const Error = WriteError;

            pub fn write(self: Self, bytes: []const u8) Error!usize {
                return writeFn(self.context, bytes);
            }

            pub fn writeAll(self: Self, bytes: []const u8) Error!void {
                var index: usize = 0;
                while (index != bytes.len) {
                    index += try self.write(bytes[index..]);
                }
            }

            pub fn print(self: Self, comptime format: []const u8, args: anytype) Error!void {
                return std.fmt.format(self, format, args);
            }

            pub fn writeByte(self: Self, byte: u8) Error!void {
                const array = [1]u8{byte};
                return self.writeAll(&array);
            }

            pub fn writeByteNTimes(self: Self, byte: u8, n: usize) Error!void {
                var bytes: [256]u8 = undefined;
                @memset(bytes[0..], byte);

                var remaining: usize = n;
                while (remaining > 0) {
                    const to_write = @min(remaining, bytes.len);
                    try self.writeAll(bytes[0..to_write]);
                    remaining -= to_write;
                }
            }

            /// Write a native-endian integer.
            pub fn writeIntNative(self: Self, comptime T: type, value: T) Error!void {
                var bytes: [@as(u16, @intCast((@as(u17, @typeInfo(T).Int.bits) + 7) / 8))]u8 = undefined;
                mem.writeIntNative(std.math.ByteAlignedInt(@TypeOf(value)), &bytes, value);
                return self.writeAll(&bytes);
            }

            /// Write a foreign-endian integer.
            pub fn writeIntForeign(self: Self, comptime T: type, value: T) Error!void {
                var bytes: [@as(u16, @intCast((@as(u17, @typeInfo(T).Int.bits) + 7) / 8))]u8 = undefined;
                mem.writeIntForeign(std.math.ByteAlignedInt(@TypeOf(value)), &bytes, value);
                return self.writeAll(&bytes);
            }

            pub fn writeIntLittle(self: Self, comptime T: type, value: T) Error!void {
                var bytes: [@as(u16, @intCast((@as(u17, @typeInfo(T).Int.bits) + 7) / 8))]u8 = undefined;
                mem.writeIntLittle(std.math.ByteAlignedInt(@TypeOf(value)), &bytes, value);
                return self.writeAll(&bytes);
            }

            pub fn writeIntBig(self: Self, comptime T: type, value: T) Error!void {
                var bytes: [@as(u16, @intCast((@as(u17, @typeInfo(T).Int.bits) + 7) / 8))]u8 = undefined;
                mem.writeIntBig(std.math.ByteAlignedInt(@TypeOf(value)), &bytes, value);
                return self.writeAll(&bytes);
            }

            pub fn writeInt(self: Self, comptime T: type, value: T, endian: std.builtin.Endian) Error!void {
                var bytes: [@as(u16, @intCast((@as(u17, @typeInfo(T).Int.bits) + 7) / 8))]u8 = undefined;
                mem.writeInt(std.math.ByteAlignedInt(@TypeOf(value)), &bytes, value, endian);
                return self.writeAll(&bytes);
            }

            pub fn writeStruct(self: Self, value: anytype) Error!void {
                // Only extern and packed structs have defined in-memory layout.

                comptime assert(@typeInfo(@TypeOf(value)).Struct.layout != .Auto);
                return self.writeAll(mem.asBytes(&value));
            }
        };
    }