Comptime
Blocks of code may be forcibly executed at compile time using the
comptime
keyword. In
this example, the variables x and y are equivalent.
const expect = @import("std").testing.expect;
fn fibonacci(n: u16) u16 {
if (n == 0 or n == 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
test "comptime blocks" {
const x = comptime fibonacci(10);
const y = comptime blk: {
break :blk fibonacci(10);
};
try expect(y == 55);
try expect(x == 55);
}
Integer literals are of the type comptime_int
. These are special in that they
have no size (they cannot be used at runtime!), and they have arbitrary
precision. comptime_int
values coerce to any integer type that can hold them.
They also coerce to floats. Character literals are of this type.
const expect = @import("std").testing.expect;
test "comptime_int" {
const a = 12;
const b = a + 10;
const c: u4 = a;
const d: f32 = b;
try expect(c == 12);
try expect(d == 22);
}
comptime_float
is also available, which internally is an f128
. These cannot
be coerced to integers, even if they hold an integer value.
Types in Zig are values of the type type
. These are available at compile time.
We have previously encountered them by checking
@TypeOf
and comparing with
other types, but we can do more.
const expect = @import("std").testing.expect;
test "branching on types" {
const a = 5;
const b: if (a < 10) f32 else i32 = 5;
try expect(b == 5);
try expect(@TypeOf(b) == f32);
}
Function parameters in Zig can be tagged as being
comptime
. This means
that the value passed to that function parameter must be known at compile time.
Let's make a function that returns a type. Notice how this function is
PascalCase, as it returns a type.
const expect = @import("std").testing.expect;
fn Matrix(
comptime T: type,
comptime width: comptime_int,
comptime height: comptime_int,
) type {
return [height][width]T;
}
test "returning a type" {
try expect(Matrix(f32, 4, 4) == [4][4]f32);
}
We can reflect upon types using the built-in
@typeInfo
, which takes
in a type
and returns a tagged union. This tagged union type can be found in
std.builtin.Type
(info on how to make use of imports and std later).
const expect = @import("std").testing.expect;
fn addSmallInts(comptime T: type, a: T, b: T) T {
return switch (@typeInfo(T)) {
.ComptimeInt => a + b,
.Int => |info| if (info.bits <= 16)
a + b
else
@compileError("ints too large"),
else => @compileError("only ints accepted"),
};
}
test "typeinfo switch" {
const x = addSmallInts(u16, 20, 30);
try expect(@TypeOf(x) == u16);
try expect(x == 50);
}
We can use the @Type
function to create a type from a
@typeInfo
.
@Type
is implemented for
most types but is notably unimplemented for enums, unions, functions, and
structs.
Here anonymous struct syntax is used with .{}
, because the T
in T{}
can be
inferred. Anonymous structs will be covered in detail later. In this example we
will get a compile error if the Int
tag isn't set.
const expect = @import("std").testing.expect;
fn GetBiggerInt(comptime T: type) type {
return @Type(.{
.Int = .{
.bits = @typeInfo(T).Int.bits + 1,
.signedness = @typeInfo(T).Int.signedness,
},
});
}
test "@Type" {
try expect(GetBiggerInt(u8) == u9);
try expect(GetBiggerInt(i31) == i32);
}
Returning a struct type is how you make generic data structures in Zig. The
usage of @This
is required
here, which gets the type of the innermost struct, union, or enum. Here
std.mem.eql
is
also used which compares two slices.
const expect = @import("std").testing.expect;
fn Vec(
comptime count: comptime_int,
comptime T: type,
) type {
return struct {
data: [count]T,
const Self = @This();
fn abs(self: Self) Self {
var tmp = Self{ .data = undefined };
for (self.data, 0..) |elem, i| {
tmp.data[i] = if (elem < 0)
-elem
else
elem;
}
return tmp;
}
fn init(data: [count]T) Self {
return Self{ .data = data };
}
};
}
const eql = @import("std").mem.eql;
test "generic vector" {
const x = Vec(3, f32).init([_]f32{ 10, -10, 5 });
const y = x.abs();
try expect(eql(f32, &y.data, &[_]f32{ 10, 10, 5 }));
}
The types of function parameters can also be inferred by using anytype
in
place of a type. @TypeOf
can then be used on the parameter.
const expect = @import("std").testing.expect;
fn plusOne(x: anytype) @TypeOf(x) {
return x + 1;
}
test "inferred function parameter" {
try expect(plusOne(@as(u32, 1)) == 2);
}
Comptime also introduces the operators ++
and **
for concatenating and
repeating arrays and slices. These operators do not work at runtime.
const expect = @import("std").testing.expect;
const eql = @import("std").mem.eql;
test "++" {
const x: [4]u8 = undefined;
const y = x[0..];
const a: [6]u8 = undefined;
const b = a[0..];
const new = y ++ b;
try expect(new.len == 10);
}
test "**" {
const pattern = [_]u8{ 0xCC, 0xAA };
const memory = pattern ** 3;
try expect(eql(u8, &memory, &[_]u8{ 0xCC, 0xAA, 0xCC, 0xAA, 0xCC, 0xAA }));
}