Skip to main content
Version: Zig 0.12.0

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.TypeInfo (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 }));
}