====== 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