Kurz: Zig hat keine Exceptions. Fehlbare Funktionen liefern eine Fehlerunion E!T (Fehler oder Wert); !T lässt den Compiler die Fehlermenge erschließen.
Teil des Kurses Zig
Fehler als Werte
Zig hat keine Exceptions. Fehlbare Funktionen liefern eine Fehlerunion E!T (Fehler oder Wert); !T lässt den Compiler die Fehlermenge erschließen.
const std = @import("std");
const RechenFehler = error{
DivisionDurchNull,
NegativeWurzel,
};
fn teile(a: i32, b: i32) RechenFehler!i32 {
if (b == 0) return error.DivisionDurchNull;
return @divTrunc(a, b);
}
fn wurzel(x: f64) RechenFehler!f64 {
if (x < 0) return RechenFehler.NegativeWurzel;
return @sqrt(x);
}
fn berechne(a: i32, b: i32) !i32 {
const q = try teile(a, b); // try: Fehler an den Aufrufer weitergeben
return q * 2;
}
pub fn main() !void {
const out = std.io.getStdOut().writer();
try out.print("{d}\n", .{try teile(10, 2)});
const r = teile(1, 0) catch |err| blk: { // catch: Fehler behandeln
try out.print("Fehler: {s}\n", .{@errorName(err)});
break :blk -1;
};
try out.print("{d}\n", .{r});
try out.print("{d}\n", .{teile(9, 0) catch 0}); // Standardwert
if (wurzel(-4)) |w| {
try out.print("{d}\n", .{w});
} else |err| {
try out.print("wurzel scheitert: {s}\n", .{@errorName(err)});
}
if (berechne(5, 0)) |_| {
try out.print("kein Fehler\n", .{});
} else |e| switch (e) { // Fehler in switch prüfen
error.DivisionDurchNull => try out.print("durch null!\n", .{}),
else => unreachable,
}
const zahl = std.fmt.parseInt(u8, "300", 10) catch |e| {
try out.print("parseInt: {s}\n", .{@errorName(e)});
return;
};
try out.print("nie erreicht {d}\n", .{zahl});
}Ausgabe:
5
Fehler: DivisionDurchNull
-1
0
wurzel scheitert: NegativeWurzel
durch null!
parseInt: OverflowWird ein Fehler bis zu main durchgereicht, beendet Zig das Programm mit einer Fehlermeldung samt Stack-Trace (im Debug-Modus). Der Compiler zwingt dich, jeden Fehler zu behandeln oder weiterzugeben.
defer und errdefer
defer führt Code am Ende des Gültigkeitsbereichs aus, ideal fürs Aufräumen. errdefer nur, wenn der Bereich mit einem Fehler verlassen wird:
const std = @import("std");
fn arbeite(out: anytype, fehler: bool) !void {
try out.print("öffne Ressource\n", .{});
defer out.print("schließe Ressource\n", .{}) catch {};
errdefer out.print("Aufräumen nach Fehler\n", .{}) catch {};
try out.print("arbeite ...\n", .{});
if (fehler) return error.Kaputt;
try out.print("fertig\n", .{});
}
pub fn main() !void {
const out = std.io.getStdOut().writer();
arbeite(out, false) catch {};
try out.print("--\n", .{});
arbeite(out, true) catch |e| try out.print("Fehler: {s}\n", .{@errorName(e)});
// defer-Reihenfolge: rückwärts
{
defer out.print("A ", .{}) catch {};
defer out.print("B ", .{}) catch {};
defer out.print("C\n", .{}) catch {};
}
}Ausgabe:
öffne Ressource
arbeite ...
fertig
schließe Ressource
--
öffne Ressource
arbeite ...
Aufräumen nach Fehler
schließe Ressource
Fehler: Kaputt
C
B ADateien
const std = @import("std");
pub fn main() !void {
const out = std.io.getStdOut().writer();
var dir = std.fs.cwd();
{
const datei = try dir.createFile("demo.txt", .{});
defer datei.close();
try datei.writeAll("Zeile 1\nZeile 2\nZeile 3\n");
}
var puffer: [256]u8 = undefined;
const datei = try dir.openFile("demo.txt", .{});
defer datei.close();
const n = try datei.readAll(&puffer);
try out.print("{d} Bytes\n", .{n});
var zeilen = std.mem.splitScalar(u8, puffer[0..n], '\n');
var zaehler: usize = 0;
while (zeilen.next()) |z| {
if (z.len == 0) continue;
zaehler += 1;
try out.print("{d}: {s}\n", .{ zaehler, z });
}
const fehler = dir.openFile("gibt_es_nicht.txt", .{});
try out.print("{any}\n", .{fehler});
try dir.deleteFile("demo.txt");
}Ausgabe:
24 Bytes
1: Zeile 1
2: Zeile 2
3: Zeile 3
error.FileNotFoundSicherheit: Panic, unreachable, assert
const std = @import("std");
pub fn main() !void {
const out = std.io.getStdOut().writer();
const arr = [_]i32{ 1, 2, 3 };
var i: usize = 0;
while (i < arr.len) : (i += 1) {
try out.print("{d} ", .{arr[i]});
}
try out.print("\n", .{});
std.debug.assert(arr.len == 3); // bricht im Debug-Modus ab, wenn falsch
// arr[5] wäre ein Panic: index out of bounds
// @panic("Selbst ausgelöst");
// unreachable: behauptet "hier kommt man nie hin"
try out.print("alles in Ordnung\n", .{});
}Ausgabe:
1 2 3
alles in OrdnungMerke
- Fehler sind Werte:
error{...},E!T,try(weiterreichen),catch(behandeln) if (res) |wert| ... else |err| ...undswitchauf Fehlerdeferräumt am Blockende auf,errdefernur bei Fehler- Dateien:
std.fs.cwd().createFile/openFile, immerdefer datei.close() - Debug-Modus fängt Überläufe, Grenzfehler und
unreachableab
Übungsaufgabe
Schreibe eine Funktion parseAlter(text) !u8, die bei ungültiger Eingabe einen eigenen Fehler liefert.
Quiz zur Selbstkontrolle
Wofür steht try?
- Gibt einen Fehler an den Aufrufer weiter, sonst liefert es den Wert (richtig)
- Fängt jeden Fehler
- Wiederholt den Aufruf
- Startet einen Thread
Wann läuft defer?
- Am Ende des umgebenden Blocks (richtig)
- Sofort
- Nur bei Fehlern
- Beim Programmstart
Gibt es Exceptions in Zig?
- Nein, Fehler sind Rückgabewerte (richtig)
- Ja, wie in Java
- Nur in Tests
- Nur für Dateien
Weiter im Kurs
Zurück: Funktionen, Arrays, Slices und Strings
Weiter: Structs, Enums, Unions und Speicherverwaltung
Alle Kapitel: Zig im Überblick