Suppose you want to write a series of functions that can chosen at runtime based on a some input. Lookup tables are very important in general as a programming concept, but to keep it simple I'll use a very trivial example, which in reality doesn't require a LUT.
pub fn main() void {
var i:usize = 5; // or maybe have it be a user input.
hiFuns[i](); // prints out "hi <i>"
}
How would you do that? Since zig doesn't support directly indexing functions, you must be able to create a namespace for each of these functions, and the most sensible way to have a variable namespace key is by creating a function that returns a struct that wraps the function! At the risk of summoning the Enterprise Java spirits we'll call this a function factory, hopefully that name will help you see what is going on here..
fn FunctionFactory(comptime i: usize) type {
return struct{
fn hiFun() void {
std.debug.print("hi {}\n", .{i});
}
};
}
Next we need to marshal these functions into an array, but this too needs to be wrapped in a function, since we can't initialize an array with a comprehension and we can't run a for loop at comptime in the outer context for initialization purpososes. That's ok, the solution is sensible:
const FnType = fn() void;
fn lookupTable(comptime count: usize) [count]FnType {
var funs: [count]FnType = undefined;
for (funs) | *f, index | {
f.* = FunctionFactory(index).hiFun;
}
return funs;
}
Finally, we have to instantiate the array. Don't forget to call lookupTable
as comptime
! This tripped me up until I was helped on the zig discord.
pub fn main() void {
const hiFuns = comptime lookupTable(10);
var i:usize = 5;
hiFuns[i]();
}
And there we have it, a small program that uses a function lookup table. This can be a big deal for program optimization! And the great thing is that Zig's way of achieving this, while it might be nonobvious at the start and slightly verbose, is consistent and follows from Zig's first principles and isn't terribly hard to read. Happy optimizing!
Top comments (2)
I'm not sure what you mean, but the following works fine for me in 0.8.0:
Your code is a bit different in that your lookupTable function both generates the functions for the lookup table and assembles them into the lookup table. If they need to be generated, that's fine, but if they're static, the above method will work with less complication.
This is for situations where you don't know the functions ahead of time: perhaps they need to be generated based on the index, perhaps the length of the array depends on the size of a datatype, e.g.
Yeah, perhaps that is not best described as "directly indexing".