EMZETT.
Login

Kurz: Mit comptime führt der Compiler Zig-Code während der Übersetzung aus. Das ersetzt Makros, Templates und Präprozessor-Tricks.

Teil des Kurses Zig

Kapitel 6 von 8 im Kurs Zig (Abschnitt „Fortgeschritten“). Mit Fortschritt, Quiz und Zertifikat auf der Lernseite.

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

  • comptime führt Code zur Übersetzungszeit aus (Tabellen, Generics, Reflection)
  • Generische Typen sind Funktionen: fn Stapel(comptime T: type) type
  • anytype und comptime T: type für generische Parameter
  • test "name" { ... } mit std.testing, zig test; std.testing.allocator findet Speicherlecks

Übungsaufgabe

Schreibe eine generische Funktion enthaelt(comptime T: type, liste: []const T, x: T) bool.

Quiz zur Selbstkontrolle

Weiter im Kurs

Zurück: Structs, Enums, Unions und Speicherverwaltung

Weiter: C-Interop und Build-System

Alle Kapitel: Zig im Überblick