Can vtable based polymorhism/interface work with comptime?

Continuing my journey with Zig and interfaces, I have come into a situation where I am stuck.

Basically it seems trying to use comptime with vtable interfaces does not work?

To illustrate, this works

const LanguageInterface = struct {
    // pointer to the logger object
    ptr: *anyopaque,
    speakFn: *const fn (ptr: *anyopaque, message: []const u8) void,

    pub fn speak(self: LanguageInterface, message: []const u8) void {
        return self.speakFn(self.ptr, message);
    }
};


const SpanishInterface = struct {
    pub fn language(self: *SpanishInterface) LanguageInterface {
        return LanguageInterface{
            .ptr = self,
            .speakFn = speak,
        };
    }

    pub fn speak(ptr: *anyopaque, message: []const u8) void {
        const self: *SpanishInterface = @ptrCast(@alignCast(ptr));
        _ = self;
        std.debug.print("{s}", .{message});
    }
};


pub fn main() !void {
    var spanish = SpanishInterface{};
    const language = spanish.language();
    language.speak("hola");
}

But this does not work

const LanguageInterface = struct {
    // pointer to the logger object
    ptr: *anyopaque,
    speakFn: *const fn (ptr: *anyopaque, comptime message: []const u8) void,

    pub fn speak(self: LanguageInterface, comptime message: []const u8) void {
        return self.speakFn(self.ptr, message);
    }
};


const SpanishInterface = struct {
    pub fn language(self: *SpanishInterface) LanguageInterface {
        return LanguageInterface{
            .ptr = self,
            .speakFn = speak,
        };
    }

    pub fn speak(ptr: *anyopaque, comptime message: []const u8) void {
        const self: *SpanishInterface = @ptrCast(@alignCast(ptr));
        _ = self;
        std.debug.print("{s}", .{message});
    }
};


pub fn main() !void {
    var spanish = SpanishInterface{};
    const language = spanish.language();
    language.speak("hola");
}

It fails to compile with this error:

zig run src/main.zig
src/main.zig:35:29: error: unable to resolve comptime value
   const language = spanish.language();
                    ~~~~~~~^~~~~~~~~
src/main.zig:35:29: note: argument to function being called at comptime must be comptime-known
src/main.zig:18:46: note: expression is evaluated at comptime because the function returns a comptime-only type 'main.LanguageInterface'
   pub fn language(self: *SpanishInterface) LanguageInterface {
                                            ^~~~~~~~~~~~~~~~~
src/main.zig:9:14: note: struct requires comptime because of this field
   speakFn: *const fn (ptr: *anyopaque, comptime message: []const u8) void,
            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/main.zig:9:14: note: function is generic
   speakFn: *const fn (ptr: *anyopaque, comptime message: []const u8) void,
            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

How can I make it work?

Or maybe I am trying to do something that does not make sense and is not intended to work…ie using comptime in this way with vtable based polymorpishm can not work

The whole point of a vtable is that the function pointers are only known at runtime, and not compile time. But in order to call a function with comptime arguments, the compiler needs to know which function to use. In the worst case the function might not even be present at compile time because it’s loaded from a dll.

Now you can achieve this with other kinds of polymophism: With tagged enums you’d have no trouble with this, since all functions are known at compile time (→ you cannot change function pointers at runtime).

Also, may I ask why you need a comptime function in the vtable?

4 Likes

It’s not clear from the example what you want to do. Here’s an example which does work, which might help illustrate why yours does not.

This is somewhat contrived, but I think playing around with it, and understanding why it works, will illustrate whether what you’re trying to accomplish is even possible, and if so, how.

const Doubler = struct {
    x: i32,
    func: *const fn (i32, i32) i32,

    fn double(d: Doubler) i32 {
        return d.func(d.x, d.x);
    }
};

fn doubleFunctionStructMaker(T: type) type {
    return struct {
        fn doubleFn(a: T, b: T) T {
            return a * b;
        }
    };
}

test "comptime shenanigans" {
    const ForDoubleFunction = doubleFunctionStructMaker(i32);
    const doubler = Doubler{
        .x = 5,
        .func = &ForDoubleFunction.doubleFn,
    };
    try std.testing.expectEqual(25, doubler.double());
}

The example I used was not illustrative enough.

So instead of having a method that takes a message, I need one that can only take a struct, and from what I see, the only way to do that is to use anytype

so basically what I tired was something like:

    pub fn speak(self: LanguageInterface, message:anytype) void {
        return self.speakFn(self.ptr, message);
    }

And apparently anytype is a compile time construct.

So I guess a better question would be, how may I use vtable polymorphism when I have a function that takes anytype ? Is that possible?

I think the example i gave was not illustrative enough of what I was trying to do.

So I guess a better question would be, how may I use vtable polymorphism when I have a function that takes anytype ? Is that possible?

Because what I wanted is something like this:

    pub fn speak(self: LanguageInterface, message:anytype) void {
        return self.speakFn(self.ptr, message);
    }

Which leads to the compile error because it seems anytype leads to comptime.

The simplest solution would be to replace anytype (which is basically a comptime interface) with a runtime interface.

1 Like