I think the best way to build up a good understanding of these things is to build very simple examples / functions and add a lot of @compileLog
calls for different parts to look at the different types and values.
I think doing this carefully you eventually learn how to find out the specific types of things and how to gain insight when you are unsure, instead of having to simulate everything in your head and hope your mental model matches reality, instead you can use @compileLog
to let the compiler tell you whether your idea of what the values are was wrong or not.
First of all anytype
isn’t actually a type it is more like a hole/placeholder that can be filled by any type
.
The language reference describes it as a declaration that can be used for function parameters:
The Zig grammar describes it as a keyword while types are handled as type expressions:
ParamType
<- KEYWORD_anytype
/ TypeExpr
So with an example like this:
const std = @import("std");
pub fn main() !void {
const A: type = type;
const B: A = u32;
const c: B = 15;
@compileLog(A);
@compileLog(B);
@compileLog(c);
}
You get output:
temp15.zig:8:5: error: found compile log statement
@compileLog(A);
^~~~~~~~~~~~~~
Compile Log Output:
@as(type, type)
@as(type, u32)
@as(u32, 15)
So you can see that type
is its own type
, that is why I can declare A
as type
and assign type
to it, which is the same as const A = type;
.
Now B
is just const B: type = u32;
, which is equal to const B = u32;
And c
is const c: u32 = 15;
.
A
and B
are types, the difference is that when you have a parameter of type type
that parameter can be set to an arbitrary type.
With B
however it is one specific type that will always be that specific type and the parameter can only be set to a value of that specific type.
(in a sense it is the same with A
just that with type
the value is an arbitrary type)
Types don’t really exist at runtime, types disappear after compilation because they get compiled into the program (you may still have some type-names, or enums, or organization patterns that remind you of types, but they don’t really exist anymore.
So a type can’t be used as a value at runtime, however types do exist at comptime and thus they can be used as values at comptime.
With this you are defining a function that takes a parameter called value
and this parameter is a value and is expected to be a type
.
fn List(comptime value: anytype) type {
return struct {
values: []const value,
};
}
With this you are defining a function that takes a parameter called value
and this parameter is a value and it can be any possible value that can exist at comptime and its type will be inferred.
But then because this line values: []const value,
uses value
as a type, it basically means that using anything that isn’t a type, for example "hello"
will give you a compile error.
temp15.zig:5:25: error: expected type 'type', found '*const [5:0]u8'
values: []const value,
^~~~~
If you try to use anytype
as if it were a type you get a compile error like this:
temp15.zig:24:21: error: expected expression, found 'anytype'
const D: type = anytype;
^~~~~~~