Why not devirtualize all functions inside std.Io.Vtable

I have recently read a post here expressing concern about bloating binary size. std.Io.Vtable was a hot topic there, because many suspected that for size-critical binaries, it causes a noticeable difference.

So my question: Why not just store functions directly and not their pointers, this would make the vtable comptime only, but change nothing else about the code (if I’m not missing something here), except the ability to change the Io strategy at runtime.

That requires that, either, Io is generic, or its comptime only (which is a non-starter)

Generics like this tend to devolve into anytype parameters, which is a bad dev experience.

There is also the very rare case where you want multiple Io instances, if they cross at runtime then generics become very messy.

This judgement is somewhat vibe-based but I dont think that this would require anytype anywhere where its not already used (if it is).

I think doing this would still allow using multiple strategies, though, just not runtime-swappable, which is a cool thing that exists right now, but I cant think of a genuinely good use.

It doesn’t require it, but people will use it.

The alternative is an extra type parameter, which I don’t mind, but since Io can be very prevalent I think people will grow tired of it quickly and switch to anytype. Which just tips the bad experience into the implementation of whatever function you are calling.

Could you show me an example / place in the stdlib because I genuinely cant think of one (which is probably a skill issue on my part)

It was a big problem with the old generic reader/writer, there are far fewer examples now since those there the major offenders.

I dont think it would be as bad with a generic Io, but I dont think its worth it.

Im not sure if you’re aware, but the zig team is tackling this from the other end by improving deveritualisation via comptime known set of possible functions a function pointer can point to.

2 Likes

If you want Io to be generic then anytype is required for every function that wants to take any Io.

This was the problem with old writers too forcing functions to be doStuff(…, writer: anytype) !void

I remember those and I really do enjoy finally having function signatures that actually help me code and not being forced to make so many structs generic but I think that this wouldnt happen when devirtualizing. The vtable would continue to use ?*anyopaque and the places where you have to pass std.Io would also remain identical, its still the same type, just that that type is comptime only.

A pointer to a comptime only type is also comptime only, or it should be, there have been recurring bugs where the compiler let cases pass.

To get around that, it would have to be generic over the vtable

Where in the code would this happen?

In the definition of the vtable.

If the vtable contains comptime only function body types, then it becomes comptime only. Even though Io holds a pointer to the vtable it would also become a comptime only type.

They only way around this is to make Io generic over the vtable, much like any other generic type, e.g. ArrayList.

That genericness is a pain point, as you don’t want to depend on a specific implementation of Io you want to take any Io. You need to either take an extra type parameter everywhere or anytype.

There is also the option of a global alias, but that is only a solution for applications, not libraries which may also need to do I/O.

1 Like

Oh yep I see it now, thank you for your patience :sweat_smile:

I just came back to my pc after a few minutes, damn I was confused.