Inhaltsverzeichnis

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.

Rohspeicher: alloc & peek/poke · Fehler-Konventionen · 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

Rohspeicher: alloc & peek/poke
Fehler-Konventionen der Standardbibliothek
std.fs_ext · std.net.epoll · std.db.postgres

Letzte Aktualisierung: 2026-06-05