Function instance of a named type

with zig’s (current?) lack of “anonymous function values”, is there a suggested pattern for defining a (named) function that implements some named type…

i have some rather complex callback signatures that i’m explicitly defining as a named type:

const Callback = fn (x: u32, n: usize) bool;

pub fn start(cb: *const Callback) void {
   ...
}

...

fn myCallback(x: u32, n: usize) bool {
    ...
    return @as(bool, ...);
}

pub fn main() void {
    ...
    start(&myCallback);
}

while i do get type checking by the compiler when calling start, i do have to faithfully replicate the signature defined by Callback… what i wish i could say, of course, is something like:

pub const myCallback = CallBacK {
    // fxn body
}

until something like this is possible, what’s my most “ergonomic” alternative for authoring functions that conform to a complex signature???

one could certainly imagine an IDE providing some help here… but arguably it would be more desirable to explicitly tie myCallback to the Callback type…

i often finding myself defining “alias types” like Weight and Age which (today) simply rename something like u16 or u8… having variables explicitly declared of type Weight or Age certainly clarifies my intent, however…

suggestions???

The trick is to define a struct namespace with the function and then select the function by its name from the struct:

const foo = struct { fn foo() void {...} }.foo;

Your example becomes:

const Callback = fn (x: u32, n: usize) bool;

pub fn start(cb: *const Callback) void {
   ...
}

...

pub fn main() void {
    ...
    const myCallback = struct {
        fn myCallback(x: u32, n: usize) bool {
            ...
            return @as(bool, ...);
        }
    }.myCallback;
    start(&myCallback);
}

It is possible to use shorter names and directly pass the function:

    start(&(struct {
        fn cb(x: u32, n: usize) bool {
            ...
            return @as(bool, ...);
        }
    }.cb));

Regarding functions with complex arguments and having various callbacks, for some programs it may make sense to instead use functions that always expect a single argument the “Message” which then has various fields for the actual arguments, basically simulating message passing, where the message is some arbitrary struct.

Whether something like this is useful depends on many factors, for example whether there is polymorphic/duck-typing code involved or not.


Distinct types are another topic, one approach in status quo might be to use single field structs like:

const Weight = struct { weight: u16 };

But I guess that requires manual work and is only a poor mans approximation of distinct types that ideally would work more like units of measurement and be integrated into the language semantics, instead of just adding a nominal typing burden of explicit field names.

I don’t know of any proposals for distinct types that are accepted.
Sometimes I would like to have distinct types, but I can understand why Zig may want to avoid them, to keep the language small.
This issue has a lengthy discussion that goes into lots of details request: distinct types · Issue #1595 · ziglang/zig · GitHub

1 Like

It’s not a problem meant to be solved. Consider the situation. You’re the author of your code. If you cannot readily recall the function signature then how is someone reading your code supposed to understand what it means when these functions are invoked? How do the function arguments relate to each other? Why is arg0 ahead of arg1? Who is doing what to whom?

Memento

Putting everything into a struct makes sense in such a situation. Struct fields have names so you can convey what they’re meant for. The availability of default values means that you can expand the struct later to suit an immediate need without breaking existing code. Now all you have to do is remember the name of the struct.

3 Likes

an interesting suggestion, which certainly simplifies the callback signature (without losing any type safety) while also allow my callback to be more explicit about its ultimate use…

i would now refine my original example as follows:

const CbParams = struct {
    x: u32
    n: usize,
    @"return": bool,
};

const CbFxn = *const fn(params: *CbParams) void;

pub fn start(cb: CbFxn) void { ... }

fn myCallback(params: *CbParams) void { ... }

pub fn main() {
    start(&myCallback);
}

even in situations where the callback has a “simple signature” (and the Params struct is basically empty), i have a little more safety i can’t (easily) substitute a pair of callbacks with structurally identical params…

for example, all “event handlers” in many frameworks have the same signature; all “interrupt handlers” in some RTOS have the same signature; etc…

as i’ve already learned, i can add “dummy fields” to my CbParams struct with “unique identifying names” that further suggest where they should be passed as arguments…

i could have two frameworks (A and B) with their own startA and startB functions – each accepting a callback with the same functional signature (fn(x: u32, n: usize) bool) that is encoded in CbParamsA or CbParamsB structs that internally have a (globally-unique) field that identifies the originating (A or B) framework…