#How Does Abstraction Work ( or -- Where Are My Interfaces?!? )

1 messages · Page 1 of 1 (latest)

stray sage
#

I feel like I'm still looking for "the other shoe" in zig: there doesn't seem to be anything like an interface type.

To make this rather, ahem, abstract ask concrete, here's what I want to do:

  • write a function that takes a reader and a writer, but idc what kind of reader/writer implementation is used
  • in particular, I want to call it with stdin/stdout streams from main
  • but from in-memory FixedBufferStream / ArrayList streams in a test block

Maybe this is possible, but nothing in the learning / docs have shown me an example like this so far using something like an interface type?

lost current
#

well, the reader and writer "interfaces" in zig are comptime-based, so they're duck-typed

#

the way to accept a reader is to just have a parameter like reader: anytype, use it as you would an instance of std.io.Reader

#

ditto for std.io.Writer

#

and yeah, your assessment is correct, there is no one unifying interface type

#

because there is not one unifying way to do interfaces

stray sage
#

yeah... can confirm that so far this is "working":

// TODO: how do we say "any Reader / any Writer"?
fn run(input: anytype, output: anytype) !void {
    _ = input;
    try output.print("hello\n", .{});
}

test "example" {
    var input = std.io.fixedBufferStream(
        \\...
    );

    var output = std.ArrayList(u8).init(std.testing.allocator);
    defer output.deinit();

    try run(input.reader(), output.writer());
    try std.testing.expectEqualStrings(
        \\NOPE
        \\
    , output.items);
}


pub fn main() !void {
    const input = std.io.bufferedReader(std.io.getStdIn().reader()).reader();
    const output = std.io.getStdOut().writer();
    try run(input, output);
}
#

but it feels terrible, since zls has no idea what can be done with input/output inside of run, so code completion is totally written off

#

tried running down this hallway, but it seem like a dead end?

tired copper
#

Zig tends to favour duck typing over interface types

#

If you want to provide more precise error messages, you can add type checking yourself using comptime and @compileError

#

The code completion issue is a problem though, not sure what can be done to fix it

soft leaf
tired copper
#

annotations might help, but comptime interpreter probably wouldn't

soft leaf
#

but the way I tend to do it is create a generic writer and use that for code completion and remove it

#

lol

tired copper
#

ig the comptime interpreter could execute test cases and try to track the types through

#

idk how fancy auguste is planning to go :)

#

sounds pretty slow either way though

lost current
#

In theory if we tweaked it to be a more concretable type it wouldn't be as bad

#

I played around with some designs, one of which was something like:

pub const ReaderIface = struct {
    Context: type,
    Error: type,
    readFn: *const anyopaque,

    pub fn Reader(comptime iface: ReaderIface) type {
        const Fn = fn(iface.Context)iface.Error!usize;
        return std.io.Reader(iface.Context, iface.Error, @ptrCast(*const Fn, iface.readFn).*);
    }
};
#

Wherein a parameter for it would be like comptime readiface: ReaderIface, reader: readiface.Reader()

tired copper
#

At that point just use comptime R: type, reader: R

lost current
#

So you'd get similar benefits to the usual comptime T: type approach

lost current
#

Whereas in my example you would

tired copper
#

ah yes, true

#

Really ugly to use though

lost current
#

It's like doing comptime T: type, slice: []const T

#

And, not necessarily. The idea would be that something implementing the reader interface would have some easy way of providing it - maybe as a comptime field

#

So like readStuff(impl.readiface, impl.reader())

tired copper
#

idk, feels like just complicating usage and reducing readability for the benefit of external tooling that not everyone is even gonna be using

obsidian portal
#

don't we got some libs out there which automate the making vtables a bit?
like I saw validate added a fieldParentPointer style dispatch, surely something exists for wide pointer style

tired copper
#

vtables don't help for comptime interfaces

soft leaf
#

I think I had a comptime hybrid for code completion with ease of use

#

I think

#

in my interface investigation

jaunty ingot
#

also note that fieldParentPointer resolution doesn't get inlined by the optimizer
it actually ends up in the compiled binary
doing interfaces like %%std.mem.Allocator has the optimizer remove the interface at compile time by inlining the calls
i can't seem to find the evidence anymore, sry for that

soft leaf
jaunty ingot
#

idk what those are

soft leaf
#

The change from fieldParentPointer to the current Interface structs

obsidian portal