====== 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 [[lyx_-_programmiersprache:sprache:std-fehlerkonventionen|Fehler-Konventionen]], die Sprachmittel in [[lyx_-_programmiersprache:sprache:exception-handling|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 [[lyx_-_programmiersprache:sprache:exit-codes|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.