167 lines
5.4 KiB
Zig
167 lines
5.4 KiB
Zig
const expect = @import("std").testing.expect;
|
|
const print = @import("std").debug.print;
|
|
|
|
// Blocks of code may be forcibly executed at compile time using the `comptime` keyword.
|
|
// In this example, the variables x and y are equivalent.
|
|
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 a way 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.
|
|
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);
|
|
}
|
|
|
|
// 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.
|
|
test "branching on types" {
|
|
const a = 5;
|
|
const b: if (a < 10) f32 else i32 = 5;
|
|
print("b: {}, {any}\n", .{ b, @TypeOf(b) });
|
|
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.
|
|
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).
|
|
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"),
|
|
// FIXME: To pass the test, we need to use the `int` tag instead of the `Int` tag and no `.Comptime` field.
|
|
// - There should be no fields `.Int` and `.ComptimeInt` in `@typeinfo(u16 or i16)` on `zig-1.15.2` version.
|
|
.int => |info| if (info.bits <= 16) a + b else @compileError("ints too large"),
|
|
else => @compileError("only ints accepted"),
|
|
};
|
|
}
|
|
|
|
test "typeinfo switch" {
|
|
const x = addSmallInts(u7, 20, 30);
|
|
try expect(@TypeOf(x) == u7);
|
|
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.
|
|
fn GetBiggerInt(comptime T: type) type {
|
|
return @Type(.{
|
|
// .Int = .{
|
|
// .bits = @typeInfo(T).Int.bits + 1,
|
|
// .signedness = @typeInfo(T).Int.signedness,
|
|
// FIXME: To pass the test, we need to use the `Int` tag instead of the `int` tag and no `.Comptime` field.
|
|
.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.
|
|
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.
|
|
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.
|
|
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 }));
|
|
}
|