comptime: Code zur Übersetzungszeit
Mit comptime führt der Compiler Zig-Code während der Übersetzung aus. Das ersetzt Makros, Templates und Präprozessor-Tricks.
const std = @import("std");
fn fib(comptime n: u32) u64 {
return if (n < 2) n else fib(n - 1) + fib(n - 2);
}
const tabelle = blk: { // Tabelle zur Übersetzungszeit berechnen
var t: [10]u32 = undefined;
for (&t, 0..) |*zelle, i| zelle.* = @intCast(i * i);
break :blk t;
};
pub fn main() !void {
const out = std.io.getStdOut().writer();
const f20 = comptime fib(20); // wird vom Compiler berechnet
try out.print("{d} {any}\n", .{ f20, tabelle });
try out.print("{d} {s}\n", .{ tabelle.len, @typeName(@TypeOf(tabelle)) });
inline for (.{ 1, "zwei", 3.5 }) |wert| { // Schleife über verschiedene Typen
try out.print("{any} ({s})\n", .{ wert, @typeName(@TypeOf(wert)) });
}
}Ausgabe
6765 { 0, 1, 4, 9, 16, 25, 36, 49, 64, 81 }
10 [10]u32
1 (comptime_int)
{ 122, 119, 101, 105 } (*const [4:0]u8)
3.5e0 (comptime_float)Generics
Ein generischer Typ ist in Zig eine Funktion, die zur Übersetzungszeit einen Typ zurückgibt:
const std = @import("std");
fn maximum(comptime T: type, a: T, b: T) T {
return if (a > b) a else b;
}
fn Stapel(comptime T: type) type {
return struct {
const Self = @This();
daten: std.ArrayList(T),
fn init(alloc: std.mem.Allocator) Self {
return .{ .daten = std.ArrayList(T).init(alloc) };
}
fn deinit(self: *Self) void {
self.daten.deinit();
}
fn push(self: *Self, x: T) !void {
try self.daten.append(x);
}
fn pop(self: *Self) ?T {
return self.daten.popOrNull();
}
fn groesse(self: Self) usize {
return self.daten.items.len;
}
};
}
fn summe(comptime T: type, liste: []const T) T {
var s: T = 0;
for (liste) |x| s += x;
return s;
}
pub fn main() !void {
const out = std.io.getStdOut().writer();
try out.print("{d} {d:.1} {c}\n", .{ maximum(i32, 3, 9), maximum(f64, 2.5, 1.5), maximum(u8, 'a', 'z') });
try out.print("{d} {d:.1}\n", .{ summe(i32, &[_]i32{ 1, 2, 3 }), summe(f32, &[_]f32{ 1.5, 2.5 }) });
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
var s = Stapel(i32).init(gpa.allocator());
defer s.deinit();
try s.push(10);
try s.push(20);
try s.push(30);
try out.print("{any} {any} {d}\n", .{ s.pop(), s.pop(), s.groesse() });
}Ausgabe
9 2.5 z 6 4.0 30 20 1
Bei Zig-Generics prüft der Compiler Typen beim Einsetzen (wie bei C++-Templates, aber mit lesbaren Fehlern und ohne Sonderbefehle). @TypeOf, @typeInfo, @hasDecl ermöglichen Reflection zur Übersetzungszeit.
Eingebaute Tests
Tests stehen direkt im Code und laufen mit zig test datei.zig:
const std = @import("std");
const expect = std.testing.expect;
const expectEqual = std.testing.expectEqual;
fn addiere(a: i32, b: i32) i32 {
return a + b;
}
test "Addition" {
try expectEqual(@as(i32, 5), addiere(2, 3));
}
test "Negative Zahlen" {
try expect(addiere(-2, -3) == -5);
}
test "Fehler erwartet" {
const ergebnis = std.fmt.parseInt(u8, "abc", 10);
try std.testing.expectError(error.InvalidCharacter, ergebnis);
}
test "Speicher ohne Lecks" {
var liste = std.ArrayList(u8).init(std.testing.allocator); // meldet Lecks!
defer liste.deinit();
try liste.append(1);
try expectEqual(@as(usize, 1), liste.items.len);
}Ausgabe von zig test:
All 4 tests passed.Ein vergessenes deinit würde der Testallocator als Leck melden. Tests werden bei zig run nicht kompiliert und kosten im fertigen Programm nichts.
Merke
comptimeführt Code zur Übersetzungszeit aus (Tabellen, Generics, Reflection)- Generische Typen sind Funktionen:
fn Stapel(comptime T: type) type anytypeundcomptime T: typefür generische Parametertest "name" { ... }mitstd.testing,zig test;std.testing.allocatorfindet Speicherlecks
Aufgabe
Schreibe eine generische Funktion enthaelt(comptime T: type, liste: []const T, x: T) bool.