Asserting function signatures at compile time in Zig

Combining Zig's comptime and reflection features to build a useful assertion function

Feel the power of comptime

I've been writing a virtual machine in Zig based on the simple LC-3 ISA. There are a some powerful features baked into Zig but overall it feels like a focused attempt at a modern C.

comptime allows you to write functionality that should be ran at compile time rather than runtime. You can now combine this with other language features for interesting effects. The popular result of this being generics. It reminds me of macro libraries that combine peculiar parts of a language to create what seems like new features.

In writing the VM, I needed a way of asserting that the functions I wrote to virtualize the assembly operations all abided by the same signature. And I wanted to assert this at compile time. By combining comptime with Zig's builtin reflection functions, I was able to write a simple function that allowed me check signature details of a function:

fn assert_instr_fn(comptime instr_fn: type) void {
    try std.testing.expect(@typeInfo(instr_fn).Fn.args.len == 1);
    try std.testing.expect(@typeInfo(instr_fn).Fn.args[0].arg_type.? == u16);
}

comptime {
    assert_instr_fn(@TypeOf(stOp));
    assert_instr_fn(@TypeOf(trapOp));
    // ...more compile time assertions...
}

The errors aren't pretty when this fails but they force me to look at what went wrong and I can potentially use a catch to output more details.

An alternative I found later: if I have a function that I want to use as the canonical reference for the type signature, I can simplify the assertion to use that with @TypeOf:

fn assert_instr_fn(comptime instr_fn: type) void {
    try std.testing.expectEqual(@typeInfo(instr_fn), @typeInfo(@TypeOf(canonicalReferenceFn)));
}

Written by