Search code examples
function-pointerszig

How to change executed method in runtime using function pointers?


I want to use a function pointer as a struct field to have a runtime choice of an executed method.

For example, I want this code to print 010:

const std = @import("std");

fn Foo() type {
    return struct {
        const Self = @This();
        const doThingFunc = fn (self: *Self) u1;
        doThing: doThingFunc = Self.doZero,

        fn doZero(self: *Self) u1 {
            self.doThing = Self.doOne;
            return 0;
        }

        fn doOne(self: *Self) u1 {
            self.doThing = Self.doZero;
            return 1;
        }
    };
}

pub fn main() void {
    var foo = Foo(){};
    std.debug.print("{d}{d}{d}", .{ foo.doThing(), foo.doThing(), foo.doThing() });
}

But I get this error instead, which makes no sense for me:

An error occurred:
example.zig:9:19: error: parameter of type '*example.Foo()' must be declared comptime
        fn doZero(self: *Self) u1 {
                  ^~~~~~~~~~~

What is a correct way to implement desired behavior with function pointers in Zig?


Solution

  • With const doThingFunc = fn (self: *Self) u1; OP code has declared a function body type, not a function pointer type. In Zig function body types are comptime-known, but function pointer types may be runtime-known. The correct declaration is:

    const doThingFunc = *const fn (self: *Self) u1;

    There is a further problem in OP code: struct methods can be called using the dot syntax, but you can't call a struct method through a function pointer using the dot syntax.

    You could solve this problem by including the missing arguments:

    std.debug.print("{d}{d}{d}", .{ foo.doThing(&foo), foo.doThing(&foo), foo.doThing(&foo) });
    

    But this seems to violate the spirit of OP code. Instead, create a public method that calls the function pointer internally. Here is a modified version of OP code that does that.

    const std = @import("std");
    
    fn Foo() type {
        return struct {
            const Self = @This();
            const DoThingFunc = *const fn (self: *Self) u1;
            do_thing: DoThingFunc = &Self.doZero,
            
            pub fn doThing(self: *Self) u1 {
                return self.do_thing(self);
            }
    
            fn doZero(self: *Self) u1 {
                self.do_thing = &Self.doOne;
                return 0;
            }
    
            fn doOne(self: *Self) u1 {
                self.do_thing = &Self.doZero;
                return 1;
            }
        };
    }
    
    pub fn main() void {
        var foo = Foo(){};
        std.debug.print("{d}{d}{d}\n",
                        .{ foo.doThing(), foo.doThing(), foo.doThing() });
    }
    

    Here is the output of the program:

    $ ./func_pointers 
    010