Zig NEWS

Isaac Yonemoto
Isaac Yonemoto

Posted on

How to make a function lookup table (LUT)

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>"
}
Enter fullscreen mode Exit fullscreen mode

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});
        }
    };
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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]();
}
Enter fullscreen mode Exit fullscreen mode

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!

Oldest comments (2)

Collapse
 
pjz profile image
Paul Jimenez

zig doesn't support directly indexing functions

I'm not sure what you mean, but the following works fine for me in 0.8.0:

fn hiFun1() void {                                                             
    std.debug.print("hi\n", .{});                                              
}                                                                              

fn hiFun2() void {                                                             
    std.debug.print("hello\n", .{});                                           
}                                                                              

const FnType = fn () void;                                                     
const hiFuns = [_]FnType{ hiFun1, hiFun2 };                                    

pub fn main() void {                                                           
    var i: usize = 1; // or maybe have it be a user input.                     
    hiFuns[i](); // prints out "hello"                                        
}
Enter fullscreen mode Exit fullscreen mode

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.

Collapse
 
ityonemo profile image
Isaac Yonemoto • Edited

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".