====== Handle-Konzept: Warum alles ein int64 ist ====== In der Lyx-Standardbibliothek ist fast jeder Ressourcentyp ein ''int64''. Eine TCP-Verbindung ist ein ''int64''. Ein PDF-Dokument ist ein ''int64''. Eine PostgreSQL-Verbindung ist ein ''int64''. Ein SQLite-Statement ist ein ''int64''. Ein epoll-Instanz-Deskriptor ist ein ''int64''. Das ist kein Zufall und keine Vereinfachung. Es ist ein bewusstes Designprinzip, das direkt aus dem POSIX-Modell abgeleitet ist. → [[lyx_-_programmiersprache:sprache:rohspeicher|Rohspeicher: alloc & peek/poke]] · [[lyx_-_programmiersprache:sprache:std-fehlerkonventionen|Fehler-Konventionen]] · [[lyx_-_programmiersprache:units|Standard Library]] ---- ===== Zwei Arten von int64-Handles ===== Lyx unterscheidet intern zwei Kategorien, die beide als ''int64'' erscheinen: ==== 1. Kernel File Descriptor (fd) ==== Ein von Linux vergebener ''int64''-Wert (typischerweise klein: 3, 4, 5 …). Der Kernel verwaltet die zugehörige Ressource. Gültige fds sind **≥ 0**, Fehler sind **< 0**. Beispiele: ''open()'', ''socket()'', ''epoll_create1()'', ''signalfd()'', ''memfd_create()'', ''inotify_init()'', ''mq_open()'' import std.fs; import std.net.epoll; var fileFd: int64 := open("/etc/hosts", 0, 0); // Kernel-fd, typisch 3–1023 var epollFd: int64 := EpollCreate(); // Kernel-fd var netFd: int64 := sca_socket(2, 1, 0); // Kernel-fd // Prüfung: fd < 0 → Fehler if (fileFd < 0) { /* open fehlgeschlagen */ } Kernel-fds müssen mit ''close(fd)'' geschlossen werden. Der Kernel gibt die zugehörigen Ressourcen frei. ==== 2. Userspace-Handle (Pointer auf alloc'd Struct) ==== Eine von ''alloc(n)'' zurückgegebene Adresse — also ein normaler Pointer, der als ''int64'' dargestellt wird. Die zugehörige Datenstruktur lebt im Heap des Prozesses. Gültige Handles sind **≠ 0**, Fehler sind **0**. Beispiele: ''SQLiteOpen'', ''PGConnect'', ''PdfCreate'', ''RedisConnect'' import std.db.sqlite; import std.db.postgres; import std.pdf.builder; var db: int64 := SQLiteOpen("/data/app.db"); // Zeiger auf SQLiteDB (24 Bytes) var pg: int64 := PGConnect(...); // Zeiger auf PGConn (120 Bytes) var doc: int64 := PdfCreate(); // Zeiger auf PDF-Dokument-Struct // Prüfung: Handle == 0 → Fehler if (db == 0) { /* SQLite-Fehler */ } Userspace-Handles müssen mit der entsprechenden ''Close''/''Free''/''Destroy''-Funktion freigegeben werden. ---- ===== Warum nicht typisierte Handles? ===== Eine naheliegende Frage: Warum kein ''type SqliteDB = int64'' oder gar ein eigener Struct-Typ? Drei Gründe: **1. Uniformität mit dem Kernel-API** Linux modelliert alles als fd: Dateien, Sockets, Pipes, Timer, Signale, epoll-Instanzen. Lyx übernimmt dieses Modell konsequent auf Userspace-Handles aus. Eine einheitliche Schnittstelle reduziert mentalen Overhead. **2. Kein Type-System-Overhead** Typisierte Handles würden zusätzliche Wrapper-Typen, Konvertierungsfunktionen und Compiler-Komplexität erzeugen. Für den Einsatz in Embedded- und Safety-Kontexten ist Einfachheit ein explizites Designziel. **3. Composability** Da alle Handles ''int64'' sind, können sie in Arrays, Structs und als Rückgabewerte ohne Konvertierung verwendet werden: // Array von Verbindungs-Handles — kein generischer Typ nötig var conns: int64[64]; var count: int64 := 0; fn AddConn(fd: int64): void { if (count < 64) { conns[count] := fd; count := count + 1; } } ---- ===== Erkenne auf einen Blick, was ein Handle ist ===== Die Konvention in der Standardbibliothek ist konsistent: ^ Muster ^ Art ^ Fehlerindikator ^ Freigabe ^ | ''open'', ''socket'', ''EpollCreate'', ''InotifyCreate'', ''MqOpen'' | Kernel-fd | ''< 0'' | ''close(fd)'' | | ''SQLiteOpen'', ''PGConnect'', ''PdfCreate'', ''RedisConnect'' | Userspace | ''== 0'' | ''SQLiteClose'' / ''PGClose'' / ''PdfFree'' / ''RedisClose'' | | ''SQLiteStmtPrepare'', ''PGStmtPrepare'' | Userspace (Sub-Handle) | ''== 0'' | ''Finalize'' / ''Close'' | | ''PdfBeginXObject'' | Userspace (negativer Index) | ''== 0'' | keine (wird vom doc verwaltet) | ---- ===== Handles weitergeben und speichern ===== Da Handles nur ''int64'' sind, können sie wie normale Werte weitergegeben werden. Die Ownership-Konvention (wer gibt frei?) ist durch den Funktionsnamen erkennbar: import std.db.postgres; // Erstellt Handle — Aufrufer ist verantwortlich für PGClose fn OpenDB(): int64 { return PGConnect("127.0.0.1", 5432, "mydb", "user", "pass"); } // Verwendet Handle — gibt ihn NICHT frei fn QueryUsers(conn: int64): int64 { return PGQuery(conn, "SELECT COUNT(*) FROM users"); } // Schließt Handle — danach nicht mehr verwenden fn CloseDB(conn: int64): void { PGClose(conn); } fn main(): int64 { var conn: int64 := OpenDB(); if (conn == 0 || PGIsConnected(conn) == 0) { return 1; } var res: int64 := QueryUsers(conn); PGFetchRow(res, 0); PrintLn(IntToStr(PGGetInt(res, 0))); PGFreeResult(res); CloseDB(conn); return 0; } ---- ===== Handles in Datenstrukturen ===== Handles können direkt in eigenen Structs (als alloc'd Byte-Puffer) gespeichert werden: import std.alloc; import std.db.postgres; import std.net.epoll; // Eigene Verbindungs-Verwaltungsstruktur pub con APP_SIZE: int64 := 24; pub con APP_PG_CONN: int64 := 0; // int64 → PGConn-Handle pub con APP_EPOLL: int64 := 8; // int64 → epoll-fd pub con APP_FLAGS: int64 := 16; // int64 → Bit-Flags fn AppCreate(pgConn: int64, epollFd: int64): int64 { var app: int64 := alloc(APP_SIZE); if (app == 0) { return 0; } poke64(app + APP_PG_CONN, pgConn); poke64(app + APP_EPOLL, epollFd); poke64(app + APP_FLAGS, 0); return app; } fn AppDestroy(app: int64): void { PGClose(peek64(app + APP_PG_CONN)); close(peek64(app + APP_EPOLL)); free(app, APP_SIZE); } ---- ===== Gültigkeitsprüfung ===== Immer prüfen, bevor ein Handle verwendet wird — besonders nach Funktionen, die fehlschlagen können: // Kernel-fd: < 0 bedeutet Fehler var fd: int64 := open("/tmp/test.txt", 1, 0644); if (fd < 0) { PrintLn("Fehler beim Öffnen"); return -1; } // Userspace-Handle: 0 bedeutet Fehler var db: int64 := SQLiteOpen("/data/app.db"); if (db == 0) { PrintLn("SQLite-Fehler"); return -1; } // Sonderfall PostgreSQL: PGConnect gibt fast nie 0 zurück (nur bei TCP-Fehler) // Zusätzlich PGIsConnected prüfen (Auth-Fehler, falsches Passwort): var pg: int64 := PGConnect("127.0.0.1", 5432, "db", "user", "pass"); if (pg == 0 || PGIsConnected(pg) == 0) { PrintLn("PG-Verbindung fehlgeschlagen"); return -1; } ---- ===== Zusammenfassung ===== ^ Frage ^ Antwort ^ | Was ist ein Handle? | Ein ''int64'' — entweder ein Kernel-fd oder eine Userspace-Heap-Adresse | | Kernel-fd oder Userspace? | Kernel: von OS vergeben (klein, ≥ 0). Userspace: von ''alloc()'' (groß, Pointer-Bereich) | | Fehlerindikator Kernel-fd | ''< 0'' | | Fehlerindikator Userspace | ''== 0'' (Ausnahme: ''PdfBeginXObject'' = negativ) | | Wer gibt frei? | Wer ''Open''/''Create''/''Connect'' aufgerufen hat — erkennbar an ''Close''/''Free''/''Destroy'' | | Kernel-fd freigeben | ''close(fd)'' | | Userspace-Handle freigeben | Passende ''*Close'' / ''*Free'' / ''*Destroy''-Funktion der Unit | → [[lyx_-_programmiersprache:sprache:rohspeicher|Rohspeicher: alloc & peek/poke]]\\ → [[lyx_-_programmiersprache:sprache:std-fehlerkonventionen|Fehler-Konventionen der Standardbibliothek]]\\ → [[lyx_-_programmiersprache:units:fs_ext|std.fs_ext]] · [[lyx_-_programmiersprache:units:net:epoll|std.net.epoll]] · [[lyx_-_programmiersprache:units:db:postgres|std.db.postgres]] Letzte Aktualisierung: 2026-06-05