Design of Io.File.MultiReader?

Why is MultiReader designed the way it is?

Why contexts: [n][@sizeOf(Context)]u8 align(@alignOf(Context)) and not just contexts: [n]Context

Why the stuff with casting Buffer to Streams that hides the data? - I just realized this is probably to avoid MultiReader having to be generic, or is there something else? EDIT: But one could just make slices into the buffers because the buffers can’t really be moved anyway after taking the streams pointer. So I don’t understand.

2 Likes

For the technical side: in Buffer it’s because it is an extern struct, so it couldn’t hold Context as it is not extern. Now Buffer is extern because it needs a fixed layout – not to “hide” data, but to keep len as the first argument for the trailing data, so the data can be unpacked by just reading the len at the pointer.

For a guess for the why: Io.Batch.init expects to have []Operation.Storage, so for the Multireader API you want to have them also in a list. Now you could design the API that it expects both as separate slices

var context_buffer: [16]Multireader.Context = undefined;
var storage_buffer: [16]Io.Operation.Storage = undefined;
mr.init(…, &context_buffer, &storage_buffer, …);

which can be done, too. It uses a bit more space so save both slices than the single pointer *Stream. And also I’d prefer the neatly packed away var buffer: MultiReader.Buffer(16) = undefined; mr.init(…, buffer.toStreams(), …).

Okay, so I see that the weirdness there is because the structs are extern because they need len to be the first. But… why?

The only two reasons that come to mind is 1. the structs actually are part of C abi, which I find hard to believe. Or 2. It is all to save 24 bytes (on 64bit system, if I count correctly) for the whole thing. Either of those reasons sounds unlikely to me.

Why not just do

pub const Streams = struct {
        contexts: []Context,
        storage: []Io.Operation.Storage,
};

pub fn Buffer(comptime n: usize) type {
    return struct {
        contexts: [n]Context,
        storage: [n]Io.Operation.Storage,
        pub fn toStreams(b: *@This()) Streams {
            return .{.contexts = &b.contexts, .storage = &b.storage};
        }

    };
}