Fehlerbehandlung im Alltag
Lyx kennt try/catch, empfiehlt es aber nicht — und die Standardbibliothek benutzt es nirgends. Stattdessen: Fehler sind Rückgabewerte. Diese Seite zeigt, wie man damit im eigenen Code arbeitet, ohne dass jede Funktion in Prüfungen erstickt.
Die Konventionen der Standardbibliothek stehen in Fehler-Konventionen, die Sprachmittel in Fehlerbehandlung.
Die vier Wege
| Weg | Wann |
|---|---|
Rückgabecode 0 = gut, negativ = Fehler | Der Normalfall; Ergebnis geht über out_buf |
Tupel (wert, fehler) | Wenn Ergebnis und Zustand zusammengehören |
Sonderwert (0, -1, null) | Bei Zeigern und Handles, wo ein Wert ohnehin ungültig ist |
panic() | Wenn der Zustand gar nicht auftreten darf — Abbruch mit Exit-Code 1 |
Dazu defer für das Aufräumen, das auf jedem Rückgabepfad passieren muss.
Beispiel
import std.io;
import std.alloc;
import std.fs;
pub con E_OK: int64 := 0;
pub con E_UNGUELTIG: int64 := 0 - 22; // -EINVAL
pub con E_ZU_KLEIN: int64 := 0 - 7; // -E2BIG-artig: Puffer reicht nicht
// 1. Rueckgabecode: 0 = gut, negativ = Fehler. Ergebnis ueber out_buf.
fn Kopiere(quelle: pchar, out_buf: int64, max: int64): int64 {
if (quelle == 0 as pchar) { return E_UNGUELTIG; }
var i: int64 := 0;
while (StrCharAt(quelle, i) != 0) {
if (i >= max - 1) { return E_ZU_KLEIN; }
poke8(out_buf + i, StrCharAt(quelle, i));
i := i + 1;
}
poke8(out_buf + i, 0);
return E_OK;
}
// 2. Tupel: zwei Werte gehoeren zusammen — Ergebnis und Zustand
fn Halbiere(x: int64): (int64, int64) {
if (x - (x / 2) * 2 != 0) { return (0, E_UNGUELTIG); }
return (x / 2, E_OK);
}
// 3. defer: raeumt auf, egal welcher Rueckgabepfad genommen wird
fn MitAufraeumen(gross: bool): int64 {
var p: int64 := alloc(64);
defer free(p, 64);
if (gross) { return 0 - 1; } // free passiert trotzdem
poke64(p, 7);
return peek64(p);
}
fn main(): int64 {
var buf: int64 := alloc(8);
var rc: int64 := Kopiere("Lyx", buf, 8);
PrintLn(StrConcat("Kopiere kurz: rc=", IntToStr(rc)));
rc := Kopiere("viel zu lang fuer acht Byte", buf, 8);
PrintLn(StrConcat("Kopiere lang: rc=", IntToStr(rc)));
var wert, zustand := Halbiere(10);
PrintLn(StrConcat(StrConcat("Halbiere(10): ", IntToStr(wert)),
StrConcat(" rc=", IntToStr(zustand))));
var w2, z2 := Halbiere(7);
PrintLn(StrConcat(StrConcat("Halbiere(7): ", IntToStr(w2)),
StrConcat(" rc=", IntToStr(z2))));
PrintLn(StrConcat("MitAufraeumen(false): ", IntToStr(MitAufraeumen(false))));
PrintLn(StrConcat("MitAufraeumen(true): ", IntToStr(MitAufraeumen(true))));
free(buf, 8);
return 0;
}
Kopiere kurz: rc=0
Kopiere lang: rc=-7
Halbiere(10): 5 rc=0
Halbiere(7): 0 rc=-22
MitAufraeumen(false): 7
MitAufraeumen(true): -1
Übersetzt und ausgeführt mit lyxc 1.1.3I.
defer statt Aufräumzweigen
fn MitAufraeumen(gross: bool): int64 {
var p: int64 := alloc(64);
defer free(p, 64); // laeuft am Blockende, egal wie er verlassen wird
if (gross) { return 0 - 1; }
poke64(p, 7);
return peek64(p);
}
Das ist der Ersatz für goto cleanup aus C und für try/finally aus Pascal. Beide Rückgabepfade im Beispiel geben den Puffer frei — sichtbar an einer Stelle, statt zweimal.
defer läuft am Ende des Blocks, in dem es steht, in umgekehrter Reihenfolge der Anmeldung.
Fehlercodes, die etwas sagen
Drei Gewohnheiten machen den Unterschied:
- Negative Zahlen für Fehler,
0für Erfolg. Dann istif (rc < 0)die einzige Prüfung, die man sich merken muss. - Bekannte Zahlen wiederverwenden: Die Standardbibliothek gibt bei Syscalls den negierten
errnozurück (-2= ENOENT,-13= EACCES,-22= EINVAL). Eigene Codes daran anzulehnen spart eine Übersetzungstabelle. - Eigene Codes als
conbenennen.E_ZU_KLEINliest sich besser als-7— und lässt sich später ändern.
Fehler weiterreichen
Der einfachste brauchbare Stil ist der frühe Ausstieg:
fn Verarbeite(pfad: pchar): int64 {
var fd: int64 := open(pfad, 0, 0);
if (fd < 0) { return fd; } // Fehlercode unveraendert nach oben
defer close(fd);
var buf: int64 := alloc(4096);
defer free(buf, 4096);
var n: int64 := read(fd, buf as pchar, 4096);
if (n < 0) { return n; }
return Auswerten(buf, n);
}
- Fehler nicht übersetzen, solange sie nur durchgereicht werden — sonst geht die Ursache verloren.
- Am Rand des Programms einmal in eine Meldung wandeln, dort mit Kontext („Datei X nicht lesbar: ENOENT„).
- Nie stillschweigend verschlucken. Ein ignorierter Rückgabewert ist der Anfang jeder schwer findbaren Fehlersuche.
Wann panic() richtig ist
panic() bricht mit Exit-Code 1 ab und ist nicht abfangbar — auch nicht in einem try-Block. Es gehört an Stellen, an denen der Zustand einen Programmierfehler bedeutet:
- Invariante verletzt („Zähler negativ, obwohl nur erhöht wird“)
- Ein Zweig, der laut Konstruktion nie erreicht wird
malloc_orpanic, wenn ein fehlender Speicher ohnehin das Ende bedeutet
Nicht für Zustände, die die Umwelt herbeiführt: fehlende Datei, abgelehnte Verbindung, ungültige Eingabe. Das sind Fehlercodes.
Wo Lyx selbst abbricht
| Auslöser | Verhalten |
|---|---|
panic(), assert | Meldung nach stderr, Exit 1 |
| Bereichsprüfung (Range-Typ, Indexgrenze) | derselbe Weg |
NaN/Inf unter @flight_crit | SIGFPE, Meldung mit Funktionsnamen, Exit 134 |
Nicht deklarierter Syscall unter @capabilities | seccomp beendet den Prozess (rc 159); mit –seccomp-trap mit Syscall-Nummer |
| Abstrakte Methode ohne Implementierung | lyxc: abstrakte Methode X.Y ohne Implementierung aufgerufen |
Die vollständige Liste der Exit-Codes steht unter Exit-Codes.
Fallstricke
- Rückgabewert ignoriert. Der Compiler verlangt nicht, dass er gelesen wird — die Prüfung ist Disziplin.
0als Fehler und als gültiger Wert. Dann braucht es das Tupel oder einenout-Parameter.- Aufräumen im Fehlerzweig vergessen.
deferdirekt hinter die Anforderung schreiben, nicht ans Blockende. panic()für Umweltfehler. Ein Programm, das bei fehlender Datei abbricht, ist im Betrieb unbrauchbar.- Fehler mehrfach übersetzt.
-2→ „Fehler 1„ → „allgemeiner Fehler“ — am Ende weiß niemand mehr, was passiert ist.
Letzte Aktualisierung: 2026-08-19 — Beispiel mit lyxc 1.1.3I übersetzt und ausgeführt.
