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, 0 für Erfolg. Dann ist if (rc < 0) die einzige Prüfung, die man sich merken muss.
  • Bekannte Zahlen wiederverwenden: Die Standardbibliothek gibt bei Syscalls den negierten errno zurück (-2 = ENOENT, -13 = EACCES, -22 = EINVAL). Eigene Codes daran anzulehnen spart eine Übersetzungstabelle.
  • Eigene Codes als con benennen. E_ZU_KLEIN liest 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.
  • 0 als Fehler und als gültiger Wert. Dann braucht es das Tupel oder einen out-Parameter.
  • Aufräumen im Fehlerzweig vergessen. defer direkt 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.