1

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?

KindFrog
  • 358
  • 4
  • 17

2 Answers2

2

fn (self: *Self) u1 is not a function pointer(it used to be).
Instead it is a compile-time only function type.

For runtime function pointers you need to use *const fn (self: *Self) u1:

        const doThingFunc = *const fn (self: *Self) u1;
        doThing: doThingFunc = &Self.doZero, // Note the reference of operator & here
QuantumDeveloper
  • 737
  • 6
  • 15
1

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
ad absurdum
  • 19,498
  • 5
  • 37
  • 60
  • This code works without `&` operators at least on v0.10.1 – KindFrog Jun 17 '23 at 20:07
  • Zig _seems_ to coerce function designators to function pointers. From much experience writing C code I have adopted the position that I shouldn't assume that something works if the spec doesn't explicitly (or implicitly) allow for it, even when that something _appears_ to work. I don't see this mentioned in the spec (I may be missing an implicit allowance for this) so I prefer to explicitly take the address of a function here. Incidentally, in C function designators _are_ converted to function pointers in most expressions. – ad absurdum Jun 18 '23 at 03:30