Hi guys,
Have a look at the following code
//! hello.zig
const person = struct {
pub fn new() person {
return .{};
}
};
fn foo(_: person) void {}
pub fn main() void {
foo(.new());
}
How Zig compiler infered that the .new call is from the person?
The decl literal syntax, named that way because it works for all declarations, not just functions but consts and vars too.
Using your example foo(.new()) is semantically identical to foo(Person.new(), in the same way you can type .{} instead of Person{}, assuming zig can infer the type.
Its just type inference! zig knows the parameters of foo, so it knows what type it should be, so searches that type for a declaration with the name. Of course, the decl type, or return type if youâre calling a function, has to be the same type or one that can coerce.
Youâll probably like to know, zigs type inference is able to see through pointers, optionals etc, so it even works in those cases.
7 Likes
In order to complete what @vulpesx said, thereâs a few things to keep in mind that you might stumble upon.
You canât use them in an expression, because the result location is then different. For example:
const r: Result = .decl.modified(...);
This wonât work because it effectively is this
const r: Result = (.decl).modified(...);
And so .decl doesnât infer from : Result. Its parent expression is [expr].modified instead of const r: Result = [expr], and so it canât get the type from here.
The only exceptions to this are the simple call and try call syntaxes, because theyâre very useful. You can do .decl(...) or try .decl(...). IMO, it should also work with .decl(...) catch but last time I tried it didnât.
This also mean that this syntax only works if the return type is the same as the namespace (save for error union). IMO it should also work with noreturn, and the optional (making .decl orelse an exception too), but last time I tried it, this wasnât the case.
It doesnât work with catch and orelse because they are branches.
The type of each branch can be different as long as they are compatible (can coerce into one or the other, or into a common type), meaning the type of each branch must be known before the type of the whole expression is.
Since a decl literal depends on knowing the final type, branching expressions canât support them as it would create circular logic.
But special cases can be supported, like try, no others currently but more could be supported in the future.
they do work for optional result types, just not in .a orelse ....
thanks. Iâll read the doc.
One would think that try is also a branch since itâs just a shorthand for catch |err| return err.
With RLS one could argue that the result type of each branch of a branching expression must be the same similar to how this is possible (and currently compiles):
const Birthday = extern struct {
year: i16,
month: u8,
day: u8,
const unknown: Birthday = .{
.day = 255,
.month = 255,
.year = 0,
};
};
pub export fn create_birthday(timestamp: u64) Birthday {
const unknown = ~@as(u64, 0);
return if (timestamp == unknown)
.unknown
else
.{
// placeholder
.year = 1900,
.month = 5,
.day = 5,
};
}
I think it is. I think @vulpesx was indicating that itâs a âspecial supported caseâ, and that others might follow in the future, too.
1 Like