– edited – See below (post 4) for an updated version of this using function prototyping –
I’ve was thinking about how to implement more transparent constraints for anytype parameters - I was thinking about ways to expose this as apart of a function declarations. So I came up with an example to demonstrate something using conditional statements in the return type.
Basically, the first argument is evaluated as the constraint, and the second argument is evaluated as the result type. This has some similarity to “Enable If” in C++…
const std = @import("std");
fn RequiresInteger(comptime constraint: type, comptime result: type) type {
return switch (@typeInfo(constraint)) {
.Int => {
return result;
},
else => {
@compileError("Constraint: must be integer type.");
}
};
}
fn genericFunction(x: anytype) RequiresInteger(@TypeOf(x), bool) {
return true;
}
test "Successful Requirement" {
const x: usize = 0;
std.debug.assert(genericFunction(x));
}
test "Failed Requirement" {
const x: struct { } = undefined;
std.debug.assert(genericFunction(x));
}
Now, this is a very simple example, but the Requires function can be arbitrarily more complicated. You can check for all kinds of things with Zig’s reflection capabilities.
The value of this is that it moves the requirements out of the function’s body and into the declaration. ZLS perfectly displays the full declaration with the requirement clause as the return, so it’s quite friendly to language tools - when typing genericFunction(), here is what ZLS shows:
fn genericFunction(x: anytype) RequiresInteger(@TypeOf(x), bool)
The error message is quite nice too:
main.zig:9:13: error: Constraint: must be integer type.
@compileError("Constraint: must be integer type.");
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
main.zig:14:47: note: called from here
fn genericFunction(x: anytype) RequiresInteger(@TypeOf(x), bool) {
Thoughts?