Here’s a neat trick you can pull at comptime with the help of arbitrary-width integer:
Suppose you want to assign a number to a set of functions based on their signatures. Functions with different arguments or return values would get different numbers. Functions with the same arguments and return values would get the same number.
First, the sample input:
const ns = struct {
fn apple(arg1: u32, arg2: u32) void {
_ = arg1;
_ = arg2;
}
fn orange(arg1: u32, arg2: u32) u32 {
return arg1 + arg2;
}
fn banana(arg1: u32, arg2: u32) void {
_ = arg1;
_ = arg2;
}
};
As you can see, apple and banana have the same signature. If you run this code:
std.debug.print("apple: {s}\n", .{@typeName(@TypeOf(ns.apple))});
std.debug.print("orange: {s}\n", .{@typeName(@TypeOf(ns.orange))});
std.debug.print("banana: {s}\n", .{@typeName(@TypeOf(ns.banana))});
you would get:
apple: fn(u32, u32) void
orange: fn(u32, u32) u32
banana: fn(u32, u32) void
Now, the code for the counter:
const counter = create: {
comptime var next = 0;
break :create struct {
fn get(comptime anything: anytype) comptime_int {
_ = anything;
const slot = next;
next += 1;
return slot;
}
};
};
Due to comptime memoization, counter.get()
will only increment the counter if the argument given is something it hasn’t seen before. Since @typeName() return the same text string for apple and banana, you should get the same number, right?
const apple_slot = counter.get(@typeName(@TypeOf(ns.apple)));
const orange_slot = counter.get(@typeName(@TypeOf(ns.orange)));
const banana_slot = counter.get(@typeName(@TypeOf(ns.banana)));
std.debug.print("{d} {d} {d}\n", .{ apple_slot, orange_slot, banana_slot });
Output:
0 1 2
Nope. This is because strings are fat-pointers. Two identical strings stored at different memory location will be considered different by Zig. Here’s where arbitrary bit-with integer comes in. By convert strings into giant integers, we can force Zig to compare at comptime the actual data that the pointers point to:
fn signature(comptime f: anytype) comptime_int {
const name = @typeName(@TypeOf(f));
comptime var int = 0;
inline for (name) |c| {
int = (int << 8) | @as(comptime_int, @intCast(c));
}
return int;
}
const apple_slot = counter.get(signature(ns.apple));
const orange_slot = counter.get(signature(ns.orange));
const banana_slot = counter.get(signature(ns.banana));
std.debug.print("\n{d} {d} {d}\n", .{ apple_slot, orange_slot, banana_slot });
Result:
0 1 0