Accessing private functions of Io and Allocator implementations

If a vtable is comptime-known, you can obtain the implementation’s non-pub functions. This allows for inlining and dead code elimination.

The code below directly calls functions whose addresses are in a vtable and has an executable size of 8.3 KiB. While I have no doubts that #31421 will be solved, for now you do not need to revert to 0.15 in order to access the “middle level” between std.Io and std.posix.

const std = @import("std");

const inl: std.builtin.CallModifier = .always_inline;

const Socket = struct {
    handle: std.Io.net.Socket.Handle,

    /// not touched by the net functions
    const stub = @constCast(&@as(std.Io.Threaded, undefined));
    const vtable = std.Io.Threaded.global_single_threaded.io().vtable;

    const netConnectUnix = vtable.netConnectUnix.*;
    const ConnectError = std.Io.net.UnixAddress.ConnectError;

    fn connect(address: std.Io.net.UnixAddress) ConnectError!Socket {
        const handle = try @call(inl, netConnectUnix, .{ stub, &address });
        return .{ .handle = handle };
    }

    const netClose = vtable.netClose.*;

    fn close(socket: Socket) void {
        @call(inl, netClose, .{ stub, &.{socket.handle} });
    }

    const netWrite = vtable.netWrite.*;
    const WriteError = std.Io.net.Stream.Writer.Error;

    fn writeAll(socket: Socket, buffer: []const u8) WriteError!void {
        var written: usize = 0;
        const data = &.{buffer[written..]};
        while (written < buffer.len)
            written += try @call(inl, netWrite, .{ stub, socket.handle, &.{}, data, 1 });
    }

    const netRead = vtable.netRead.*;
    const ReadError = std.Io.net.Stream.Reader.Error;

    fn readAll(socket: Socket, buffer: []u8) ReadError!void {
        var read: usize = 0;
        var data: [1][]u8 = .{buffer};
        while (read < buffer.len)
            read += try @call(inl, netRead, .{ stub, socket.handle, &data });
    }
};

const allocator = struct {
    /// this too is discarded in the vtable functions
    const stub = @constCast(&@as(std.heap.BrkAllocator, undefined));
    const vtable = std.heap.BrkAllocator.vtable;

    fn allocSlice(byte_count: usize) std.mem.Allocator.Error![]u8 {
        const ret = @returnAddress();
        const pointer = vtable.alloc(stub, byte_count, .of(u8), ret) orelse
            return error.OutOfMemory;
        return pointer[0..byte_count];
    }

    fn freeSlice(bytes: []u8) void {
        const ret = @returnAddress();
        if (bytes.len > 0)
            vtable.free(stub, bytes, .of(u8), ret);
    }
};

pub fn main() !noreturn {
    defer std.process.exit(0);

    const path: std.Io.net.UnixAddress =
        .{ .path = "/run/user/1000/sway-ipc.1000.654.sock" };
    const socket: Socket = try .connect(path);
    defer socket.close();
    try socket.writeAll("i3-ipc\x0a\x00\x00\x00\x00\x00\x00\x00move right");
    var header: [14]u8 = undefined;
    try socket.readAll(&header);

    const message_length: u32 = @bitCast(header[6..10].*);
    const message = try allocator.allocSlice(message_length);
    defer allocator.freeSlice(message);
    try socket.readAll(message);
    std.debug.assert(std.mem.eql(u8, message, "[ { \"success\": true } ]"));
}