Answer to my riddle: It has to do with type the equivalency rules introduced in 0.12. First, there’s two broad classes: types with namespaces and those without. Those without are easy: The same definition is the same type. Eg, one u8 is another u8.
const assert = @import("std").debug.assert;
pub fn main() void {
{
const A = u8;
const B = u8;
assert(A == B);
}
{ // Tuples, despite using the struct keyword, are not namespace types.
const A = struct { u8 };
const B = struct { u8 };
assert(A == B);
}
}
Types with namespace are considered the same type under two conditions:
- They have the same source location:
const assert = @import("std").debug.assert;
pub fn main() void {
const A = struct { foo: u8 };
const B = struct { foo: u8 };
assert(A != B);
assert(A == A);
assert(B == B);
}
- The type captures the same values. So in
const assert = @import("std").debug.assert;
pub fn main() void {
{
const A = Foo(1, 2);
const B = Foo(2, 1);
assert(A == B);
}
{
const A = Foo(0, 1);
const B = Foo(0, 2);
assert(A != B);
}
}
fn Foo(a: comptime_int, b: comptime_int) type {
const c = a + b;
return struct {
comptime {
_ = c;
}
};
}
Despite Foo(1,2) and Foo(2,1) having different parameters, they both add to 3, so the value captured by the struct declaration is the same, so they’re the same type.
See 0.12.0 Release Notes ⚡ The Zig Programming Language and Proposal: namespace type equivalence based on AST node + captures · Issue #18816 · ziglang/zig · GitHub
Aside: The reason you’re seeing three instances of foo in the original puzzle has to do with memoization. That is, when functions are called with the same comptime parameters (of which comptime_int is implicitly), the compiler only runs it once during semantic analysis. Since there’s three different variations of comptime parameters, they’re three runs. Since two of those runs result in the same captured value, there’s only 2 different types declared.
The container variable declaration in my original puzzle is a simple offshoot of the type equivalency rules. Same type, same variables. Different type, different variables.
You can remove the memoization by using inline fn in the above example (not that you should, but you can), and the types are still the same.
inline fn Foo(a: comptime_int, b: comptime_int) type {
const c = a + b;
return struct {
comptime {
_ = c;
}
};
}