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 } ]"));
}