====== Fehler-Konventionen der Standardbibliothek ======
Die Lyx-Standardbibliothek hat kein ''try''/''catch'', keine Exceptions, keine impliziten Fehlerzustände. Jede Funktion, die fehlschlagen kann, signalisiert das über ihren Rückgabewert — immer sichtbar, immer explizit. Diese Seite erklärt die vier Konventionen, die sich quer durch alle std-Units ziehen.
→ [[lyx_-_programmiersprache:sprache:exception-handling|Fehlerbehandlung (Sprachebene)]] · [[lyx_-_programmiersprache:sprache:handles-und-fd|Handle-Konzept]] · [[lyx_-_programmiersprache:sprache:rohspeicher|Rohspeicher]]
----
===== Konvention 1: Kernel-Syscall → Rückgabe < 0 =====
Alle Wrapper um Linux-Syscalls (''open'', ''read'', ''write'', ''socket'', ''epoll_wait'' …) geben bei Fehler einen negativen Wert zurück. Dieser Wert ist der negierte errno-Code.
import std.fs;
var fd: int64 := open("/etc/hosts", 0, 0);
if (fd < 0) {
// fd enthält negierten errno: z.B. -2 = ENOENT, -13 = EACCES
PrintLn("open fehlgeschlagen: errno = " + IntToStr(0 - fd));
return -1;
}
var n: int64 := read(fd, buf, 4096);
if (n < 0) {
// Lesefehler
close(fd);
return -1;
}
if (n == 0) {
// EOF — kein Fehler, aber keine Daten mehr
}
^ Rückgabewert ^ Bedeutung ^
| ''≥ 0'' | Erfolg (oft: Anzahl gelesener Bytes, neuer fd, 0 = OK) |
| ''< 0'' | Fehler; Wert = ''-errno'' (z.B. ''-2'' = ENOENT) |
Alle Low-Level-Units nutzen diese Konvention: ''[[lyx_-_programmiersprache:units:fs_ext|std.fs_ext]]'', ''[[lyx_-_programmiersprache:units:pipe|std.pipe]]'', ''[[lyx_-_programmiersprache:units:signals|std.signals]]'', ''[[lyx_-_programmiersprache:units:sched|std.sched]]'', ''[[lyx_-_programmiersprache:units:mmap_ext|std.mmap_ext]]'', ''[[lyx_-_programmiersprache:units:net:epoll|std.net.epoll]]''.
----
===== Konvention 2: Userspace-Handle → Rückgabe 0 bei Fehler =====
Funktionen, die einen Userspace-Handle (Pointer auf alloc'd Struct) erzeugen, geben **0** zurück wenn die Erstellung fehlschlägt.
import std.db.sqlite;
import std.db.postgres;
// SQLite: 0 = Fehler
var db: int64 := SQLiteOpen("/data/app.db");
if (db == 0) {
PrintLn("SQLite nicht geöffnet");
return -1;
}
// Prepared Statement: 0 = Fehler (SQL-Syntaxfehler, Tabelle nicht vorhanden …)
var stmt: int64 := SQLiteStmtPrepare(db, "SELECT * FROM users");
if (stmt == 0) {
Print("Prepare fehlgeschlagen: " + SQLiteErrmsg(db));
SQLiteClose(db);
return -1;
}
Units die dieser Konvention folgen: ''[[lyx_-_programmiersprache:units:db:sqlite|std.db.sqlite]]'', ''[[lyx_-_programmiersprache:units:pdf:builder|std.pdf.builder]]'' (intern), ''[[lyx_-_programmiersprache:units:net:socket|std.net.socket]]''.
----
===== Konvention 3: Zweistufige Prüfung (PostgreSQL-Muster) =====
Manche Verbindungen scheitern zweistufig: Die TCP-Verbindung kann erfolgreich sein, aber Auth oder Protokoll-Handshake danach fehlschlagen. Solche Units haben eine ''IsConnected''-Funktion.
import std.db.postgres;
var conn: int64 := PGConnect("127.0.0.1", 5432, "mydb", "user", "wrongpass");
// PGConnect gibt 0 zurück NUR bei TCP-Fehler (Host nicht erreichbar).
// Bei falschem Passwort liefert es einen gültigen conn != 0.
if (conn == 0) {
PrintLn("TCP-Verbindung fehlgeschlagen");
return -1;
}
// Zweite Prüfung: Auth- oder Protokollfehler
if (PGIsConnected(conn) == 0) {
PrintLn("Verbindung aufgebaut, aber Auth-Fehler: " + PGError(conn));
PGClose(conn);
return -1;
}
// Jetzt ist conn sicher verwendbar
> Diese Konvention gilt für alle Protokolle mit einem mehrstufigen Handshake (TCP-Verbindung + Authentifizierung). Immer **beide** Prüfungen durchführen.
----
===== Konvention 4: Fehlercode-Konstanten =====
Einige Units definieren eigene Rückgabe-Code-Konstanten anstelle von negativen Zahlen oder 0/1. Das trifft vor allem auf Units zu, die externe Protokolle wrappen.
==== SQLite ====
import std.db.sqlite;
var rc: int64 := SQLiteStmtStep(stmt);
if (rc == SQLITE_ROW) {
// Zeile verfügbar — Column-Accessoren verwenden
var name: pchar := SQLiteColumnText(stmt, 0);
PrintLn(name);
}
else if (rc == SQLITE_DONE) {
// Alle Zeilen verarbeitet — kein Fehler
}
else {
// Fehler: rc enthält SQLite-Fehlercode
PrintLn("Step-Fehler: " + IntToStr(rc));
// SQLITE_BUSY=5, SQLITE_ERROR=1, SQLITE_CONSTRAINT=19 …
}
^ Konstante ^ Wert ^ Wann ^
| ''SQLITE_OK'' | 0 | Erfolg (nicht Step/Exec) |
| ''SQLITE_ROW'' | 100 | Nächste Zeile verfügbar |
| ''SQLITE_DONE'' | 101 | Alle Zeilen verarbeitet |
| alle anderen | 1–99 | Fehler |
----
===== Fehler weitergeben =====
Da Lyx keine Exceptions hat, wird ein Fehler durch Rückgabe eines Fehlerwerts nach oben propagiert. Das übliche Muster:
import std.db.sqlite;
import std.alloc;
// Gibt 0 bei Erfolg, -1 bei Fehler zurück
fn LoadConfig(path: pchar): int64 {
var db: int64 := SQLiteOpen(path);
if (db == 0) { return -1; }
var stmt: int64 := SQLiteStmtPrepare(db, "SELECT key, value FROM config");
if (stmt == 0) {
SQLiteClose(db);
return -1;
}
while (SQLiteStmtStep(stmt) == SQLITE_ROW) {
var key: pchar := SQLiteColumnText(stmt, 0);
var val: pchar := SQLiteColumnText(stmt, 1);
ApplyConfig(key, val);
}
SQLiteStmtFinalize(stmt);
SQLiteClose(db);
return 0; // Erfolg
}
fn main(): int64 {
if (LoadConfig("/data/app.db") != 0) {
PrintLn("Konfiguration konnte nicht geladen werden");
return 1;
}
return 0;
}
----
===== Ressourcen bei Fehler freigeben =====
Der häufigste Fehler in Lyx-Code: Bei einem Fehler in der Mitte einer Initialisierungssequenz werden bereits erstellte Ressourcen nicht freigegeben.
// Schlecht — Memory/Resource Leak bei Fehler:
fn BadInit(): int64 {
var fd: int64 := open("/data/config", 0, 0);
var db: int64 := SQLiteOpen("/data/app.db");
var conn: int64 := PGConnect("localhost", 5432, "db", "u", "p");
if (PGIsConnected(conn) == 0) {
return -1; // fd und db wurden nie geschlossen!
}
return 0;
}
// Richtig — Cleanup bei jedem Fehlerfall:
fn GoodInit(): int64 {
var fd: int64 := open("/data/config", 0, 0);
if (fd < 0) { return -1; }
var db: int64 := SQLiteOpen("/data/app.db");
if (db == 0) {
close(fd);
return -1;
}
var conn: int64 := PGConnect("localhost", 5432, "db", "u", "p");
if (conn == 0 || PGIsConnected(conn) == 0) {
SQLiteClose(db);
close(fd);
return -1;
}
// Alles OK — Ressourcen werden von der aufrufenden Schicht verwaltet
return 0;
}
> **Faustregel:** Jede Ressource, die nach einem gescheiterten Schritt noch offen ist, muss in dem Fehler-Branch explizit geschlossen werden. Lyx hat kein ''defer'' und keine Destruktoren.
----
===== Schnell-Referenz =====
^ Situation ^ Fehlerindikator ^ Beispiel ^
| Syscall / Low-Level I/O | ''< 0'' | ''open'', ''read'', ''EpollCreate'' |
| Handle-Erstellung (Userspace) | ''== 0'' | ''SQLiteOpen'', ''PdfCreate'' |
| Mehrstufiger Handshake | ''== 0'' + ''IsConnected == 0'' | ''PGConnect'' + ''PGIsConnected'' |
| SQLite Step | ''SQLITE_ROW'' / ''SQLITE_DONE'' / Fehlercode | ''SQLiteStmtStep'' |
| Binding / Column-Accessor | ''bool'' (true = Erfolg) | ''SQLiteBindInt'', ''SQLiteColumnIsNull'' |
| Ressource freigeben | kein Rückgabewert | ''SQLiteClose'', ''PGClose'', ''PdfFree'' |
→ [[lyx_-_programmiersprache:sprache:exception-handling|Fehlerbehandlung auf Sprachebene (Result, Tuple, panic)]]\\
→ [[lyx_-_programmiersprache:sprache:handles-und-fd|Handle-Konzept — Kernel-fd vs. Userspace-Handle]]\\
→ [[lyx_-_programmiersprache:units:error|std.error — errno-Konstanten und Fehlermeldungen]]
Letzte Aktualisierung: 2026-06-05