Function as struct field

Hello alls !

I’m writing code as a hobby, and learning zig writing little toys. For one of them, I was hoping to have a function as field of a struct. My difficulty is that the function will be unique to each instance of the struct.

I would like to write something like this, but can’t find the correct way to do so:

const std = @import("std");

const Event = struct {
	state: u8,
	text: []const u8,
	condition: fn(*Event) bool,
};

pub fn main() void {
	const event = Event {
		.state = 1,
		.text = "blah blah",
		.condition = fn (self: *Event) bool {
			if (self.state == 1) {
				return true;
			}
		},
	};
	if (event.condition() == true) {
		std.debug.print("event: {}\n", .{event.text});
	}
}

Is there a proper way to express this in Zig 0.11.0 ?

You need a function pointer:

const funcPtr = *const fn(args...) void;
f_ptr: funcPtr = &nameOfAFunc;

Thank you ! I still can’t find how to express this within a struct…

const std = @import("std");
 2
 3 const funcPtr = *const fn(*Event) bool;
 4
 5
 6 const Event = struct {
 7     state: u8,
 8     text: []const u8,
 9     condition: funcPtr,
10 };
11
12 fn myCondition(self: *Event) bool {
13     if (self.state == 1) {
14         return true;
15     }
16     return false;
17 }
18
19 pub fn main() void {
20     const event = Event {
21         .state = 1,
22         .text = "blah blah",
23         .condition = &myCondition,
24     };
25     if (event.condition() == true) {
26         std.debug.print("event: {}\n", .{event.text});
27     }
28 }
error: expected 1 argument(s), found 0
 if (event.condition() == true) {

It does not matter whether a function pointer is a filed of a struct or a standalone variable, syntax is the same.

const std = @import("std");

const funcPtr = *const fn (*const Event) bool;

const Event = struct {
    state: u8,
    text: []const u8,
    condition: funcPtr,
};

fn myCondition(self: *const Event) bool {
    return (self.state == 1);
}

pub fn main() void {
    const event = Event{
        .state = 1,
        .text = "blah blah",
        .condition = &myCondition,
    };
    if (event.condition(&event)) {
        std.debug.print("event: {s}\n", .{event.text});
    }
}

3 Likes

Thank you for the answers !

I’m a bit sad I can’t define the function while instanciating the struct, and use the syntactical sugar to call it without argument. But it will do !

Right now zig does not support anonymous functions - you have to define them “inside” something - struct, union, enum, or root level of a file.

My little example, just in case (a sort of dynamic dispatch via virtual method table, with one element in this case):

const std = @import("std");
const log = std.debug.print;

const methodPtr = *const fn(c: *Class) void;

const Class = struct {
    x: u32,
    m: methodPtr,

    fn callMethod(i: *Class) void {
        i.m(i);
    }
};

pub fn main() void {
    var inst1 = Class{.x = 1, .m = &m1};
    var inst2 = Class{.x = 2, .m = &m2};
    inst1.callMethod();
    inst2.callMethod();
}

fn m1(i: *Class) void {
    log("my x is {}\n", .{i.x});
    log("I can do this\n", .{});
}


fn m2(i: *Class) void {
    log("my x is {}\n", .{i.x});
    log("I can do that\n", .{});
}

Also, when I see the words “state” and “event” close to each other,
I immediately start to guess that this is about “event driven state machines” or so.
EDSM, as I call it, is a design methodology which I advocate here and there :slight_smile:
If you are interested you can take a look at my GitHub repos with edsm in names (nick is the same as on this forum, dee0xeed). There are various versions of the engine and, If I remember right, the most recent was here.

Just a little style note here: In all these examples, funcPtr and methodPtr are types so they should be PascalCase: FuncPtr and MethodPtr.

Also, since they’re used only once, why not just use the type directly in the struct. field def:

m: *const fn(c: *Class) void,
2 Likes

TBH, I do not know why I used camelCase for them, I depicted it from some code I wrote before (probably at that time I did not think right and was not realizing these should be in PascalCase).

And, BTW, here is small excerpt from /opt/zig-0.11/lib/std$ grep -rI "= \*const fn":

os/plan9.zig:    pub const handler_fn = *const fn (c_int) callconv(.C) void;
os/plan9.zig:    pub const sigaction_fn = *const fn (c_int, *const siginfo_t, ?*const anyopaque) callconv(.C) void;
os/windows/ws2_32.zig:pub const LPCONDITIONPROC = *const fn (

:grin:

Because at a later time they may be used more than once, why not declare the type in advance?

Thank you all for the detailed explanations.

@dee0xeed : I will study event driven state machines and your implementation, as indeed, it might be a solution to do what I’m trying to do.

1 Like