Specific Content
Function signature:
pub fn satisfies(comptime T: type, comptime I: type) bool {
...
}
pub fn assertSatisfies(comptime T: type, comptime I: type) void {
...
}
assertSatisfies function is the same as satisfies, the only difference is that when it finds that the matching rule is not met, it will tell you the reason for the dissatisfaction in the compilation error message.
Specific rules:
The function returns true when the declarations of type I can match any subset of type T—that is, when type T is “shaped like” type I. Before detailing the rules, I should remind you that these rules are actually not complicated; they are very easy to understand through code, they are just not easy to describe in words—much like addition, subtraction, multiplication, and division are complex to define mathematically, but that does not affect our ability to use them!
Matching Rules
1. Type Declarations
Type declarations require the same name and the same container kind. That is, if a type declaration A in I is assigned struct{}, then the same-named type in T must also be of struct type.
pub const I = struct {
pub const A = struct{};
};
// Satisfies
pub const T = struct {
pub const A = struct{};
};
// Does not satisfy
pub const T = struct {
pub const A = enum{};
};
In other words, the keyword before the braces that defines the type must match, while the contents inside the braces are ignored (except for tuples). This includes:
struct
packed struct
enum
union
union(enum)
union(T) // where T follows the same matching rules as constant types below
opaque
error
In addition, there are several special cases for type declaration matching:
type: matches any type.comptime_int: matches any integer type only.comptime_float: matches any floating-point type only.struct{T, ...}(tuple): matches any one of the types listed in the tuple members.
2. Constant Declarations
Constants require the same name and the same type. That is, if member a in I is of type u32, then member a in T must also be of type u32.
pub const I = struct {
pub const a: u32 = undefined;
pub const b: [4]f64 = undefined;
};
// Satisfies
pub const T = struct {
pub const a: u32 = 10;
pub const b: [4]f64 = .{1.0, 2.0, 3.0, 4.0};
};
// Does not satisfy
pub const T = struct {
pub const a: u64 = 100;
pub const b: [4]f64 = .{1.0, 2.0, 3.0, 4.0};
};
There is one special case for constant matching: if the constant’s type is defined inside I, then the same-named declaration in T should have the equivalent type defined in T, not the type defined in I. For example:
pub const I = struct {
pub const T = struct{};
pub const a: T = undefined;
};
// Should be:
pub const A = struct {
pub const T = struct {
x: usize,
y: usize,
};
pub const a: T = .{
.x = 100,
.y = 200,
};
};
// Not:
pub const A = struct {
pub const T = struct{};
pub const a: I.T = undefined;
};
Because I is merely a template for checking; its values are generally considered meaningless. If a type is truly intended for global use, it should not be placed inside I.
3. Function Declarations
Functions require duck‑type callability. That is, the parameters and return type must mimic those declared in I, while calling conventions and other attributes may differ and are left to compile‑time handling. For example:
pub const I = struct {
pub fn foo(a: i32, b: f64) i32 {
_ = .{a, b}; // only to suppress unused variable errors
return undefined;
}
};
// Satisfies
pub const T = struct {
pub fn foo(a: i32, b: f64) i32 {
...
}
};
// Does not satisfy
pub const T = struct {
pub fn foo(a: i32, b: f32) i32 {
...
}
};
As with constant declarations, for function declarations that reference types defined inside I, the corresponding types in the same‑named function in T should be looked up in T, including Self.
Additionally, there is one special case for function declarations: if the first parameter of the function in T is *anyopaque / *const anyopaque, then it shall be allowed to match both *anyopaque / *const anyopaque and *Self / *const Self in I.
4.Top‑Level Types
Matching the container kind of the top‑level types (I and T themselves) is left to the user to check directly.
Purpose
Improve the user experience of ‘anytype’.
and so on…
Reference
Verafahn/vftrait: A library for duck type checking during compilation.
This is just an exploratory implementation, it does not achieve all the features I mentioned. But I think there are many usage scenarios for this function, and it can already be included in the standard library.