Jede Lyx-Quelldatei ist gleichzeitig ein Modul (in Lyx-Terminologie: eine Unit). Die Unit-Deklaration am Dateianfang legt den Namensraum fest. import lädt eine andere Unit und macht ihre öffentlichen Symbole sichtbar. pub steuert, welche Symbole exportiert werden.
Das System ist bewusst einfach: keine Pakete, keine Versionsverwaltung im Sprachkern, keine zyklischen Importe. Eine Unit = eine Datei = ein Namensraum.
→ Syntax-Referenz · Erste Schritte · Standard Library
Jede .lyx-Datei beginnt mit einer unit-Deklaration:
unit my_module;
Konventionen:
.lyx-Endungnet/http_client.lyx → unit net.http_client;unit-Zeile steht als erste Zeile der Datei, vor jedem Import und jedem Kommentar.
// Datei: sensors/temp_reader.lyx
unit sensors.temp_reader;
import std.io;
import std.math;
Diese Punkte sind Konventionen, keine geprüften Regeln. Der Import löst allein über den Dateipfad auf; dieunit-Zeile wird geparst, aber nicht ausgewertet. Ein abweichender Name (unit ganz.anderer.name;), ein Großbuchstabe, ein Kommentar oder Import davor — und selbst eine ganz fehlendeunit-Zeile — werden anstandslos übersetzt.
Halten Sie die Konvention trotzdem ein: Sie ist die einzige Stelle, an der die Datei ihre eigene Modulzugehörigkeit benennt, und beim Verschieben von Dateien läuft sonst still auseinander, was zusammengehört.
(Issue #1237)
Imports folgen direkt auf die Unit-Deklaration, vor allen anderen Deklarationen:
unit my_app;
// Standardbibliothek
import std.io;
import std.string;
import std.net.socket;
// Eigene Units
import sensors.temp_reader;
import utils.math_helpers;
sema error: zyklischer Import erkannt: vv.cb → vv.ca.unit vv.se; mit import vv.se;) wird dagegen ohne Meldung übersetzt — er ist wirkungslos, aber kein Fehler.
Nach dem Import sind alle pub-Symbole der Ziel-Unit direkt (unqualifiziert) sichtbar:
import std.string;
fn Greet(name: pchar): void {
var upper: pchar := alloc(256);
StrToUpper(upper, name); // kein "string." Prefix nötig
Print(upper);
}
Wenn zwei importierte Units dasselbe Symbol exportieren, entsteht eine Namenskollision. Der Compiler meldet das als Fehler. Lösung: Einen der Importe weglassen oder die Symbole umbenennen (derzeit kein Alias-Import).
Standardmäßig ist alles privat — nur innerhalb der eigenen Unit sichtbar. pub macht ein Symbol exportierbar:
Seit lyxc 1.0.11: Sichtbarkeit wird beim Import durchgesetzt. Frühere Compiler-Versionen wertetenpubbeim Import nicht aus — unit-private Symbole waren faktisch global sichtbar. Code, der versehentlich ein privates Symbol einer fremden Unit benutzt hat, übersetzt jetzt nicht mehr. Die Lösung ist, das Symbol in der anbietenden Unit alspubzu markieren, wenn es tatsächlich Schnittstelle sein soll.
Private Symbole beenden die Suche nicht, sondern werden übersprungen: Einimport std.alloc; alloc(16)findet weiterhin das öffentlichealloc, auch wenn eine andere importierte Unit ein gleichnamiges privates besitzt. Gemeldet wird erst, wenn es überhaupt keine öffentliche Fassung gibt.
unit math_utils;
// Öffentlich — von anderen Units importierbar
pub fn Square(x: int64): int64 {
return x * x;
}
// Privat — nur innerhalb math_utils sichtbar
fn SquareInternal(x: int64): int64 {
return x * x;
}
unit constants;
pub con PI: f64 := 3.14159265358979;
pub con MAX_CONN: int64 := 1024;
con INTERNAL_BUF: int64 := 4096; // privat
unit config;
pub var LogLevel: int64 := 2; // Von außen lesbar und schreibbar
var _internalState: int64 := 0; // Privat
Öffentliche Variablen (pub var) sind selten sinnvoll — sie erzeugen globalen Zustand der von jeder importierenden Unit verändert werden kann. Bevorzugepub fn Get…()/pub fn Set…()Accessoren.
unit geometry;
// Öffentlicher Typ — andere Units können Point verwenden
pub type Point = struct {
x: f64;
y: f64;
};
// Öffentliches Enum
pub enum Axis { X, Y, Z }
// Privater Hilfstyp — nur intern
type InternalMatrix = struct { data: int64; };
Ein Typ in einer öffentlichen Signatur muss selbstpubsein. WirdInternalMatrixim Rückgabetyp oder Parameter einerpub fnverwendet, kann die importierende Unit den Aufruf nicht übersetzen:sema error: 'InternalMatrix' ist in Unit 'geometry' nicht pub.
Die Prüfung hängt daran, dass der Typ ausgeschrieben wird. Sie greift beivar m: InternalMatrix := Mach();— nicht aber beivar m := Mach();. Die Kurzform übersetzt anstandslos, und der Wert ist seit lyxc 1.0.15E auch brauchbar: Der nicht-pubTyp wandert damit ungeprüft über die Unit-Grenze.
Ein zweiter Fall bleibt:var g := a;mit einer Struct-Variablen als Initialisierer übernimmt die Referenzsemantik nicht —g.a := 9lässta.aunverändert, währendvar g: S := a;korrekt auf dasselbe Objekt zeigt. Schreiben Sie den Typ bei Structs aus.
extern fn … link-Deklarationen brauchen kein pub. Bei Units, deren Zweck gerade das Anbieten von FFI-Bindungen ist, bleiben sie ohne Sichtbarkeitsmarkierung nutzbar — std/audio/alsa.lyx etwa trägt an keiner snd_pcm_*-Zeile ein pub.
Lyx hat keine explizite Namespace-Syntax — jede Unit ist ihr Namensraum. Nach dem Import sind alle pub-Symbole direkt im lokalen Scope sichtbar.
import std.db.sqlite; // exportiert: SQLiteOpen, SQLiteClose, SQLITE_ROW, ...
import std.db.postgres; // exportiert: PGConnect, PGClose, PGQuery, ...
// Kein Konflikt — unterschiedliche Präfixe per Konvention (SQLite* vs. PG*)
var db: int64 := SQLiteOpen("/data/app.db");
var conn: int64 := PGConnect("localhost", 5432, "mydb", "user", "pass");
Die Standardbibliothek nutzt überwiegend Unit-spezifische Präfixe als Konvention, um Kollisionen ohne Namespace-Qualifier zu vermeiden:
| Unit | Prefix-Konvention | Beispiel |
|---|---|---|
std.db.sqlite | SQLite* | SQLiteOpen, SQLITE_ROW |
std.db.postgres | PG* | PGConnect, PGQuery |
std.pdf.builder | Pdf* | PdfCreate, PdfSave |
std.net.socket | TCP* / UDP* / Socket* | TCPListenerNew, UDPSocketSendTo |
std.string | Str* / Char* | StrFind, CharToUpper |
std.crypto.aes | AES* | AESEncryptBlock |
Eigene Units sollten dasselbe Muster verwenden — kurzer, eindeutiger Präfix vor allen öffentlichen Symbolen.
In der Standardbibliothek ist die Konvention nicht durchgehalten. 110 der rund 5100 öffentlichen Funktionen werden von mehr als einer Unit exportiert; 31 Unit-Paare lassen sich deshalb nicht gemeinsam importieren. Betroffen sind unter anderem naheliegende Kombinationen:
^ Paar ^ kollidierende Namen ^
|std.math+std.stats|Clamp64,InRange64|
|std.fs+std.yamlbzw.std.ini|WriteFile|
|std.ini+std.yaml| 15 Namen, u. a.ParseString,GetBool|
|std.base64+std.url|Encode,Decode,IsValid|
|std.math+std.ml|AbsF64,ExpF64,LogF64,SqrtF64|
Der Fehler entsteht bereits beim Import — das kollidierende Symbol muss nicht einmal verwendet werden. Einen Ausweg gibt es derzeit nicht: Alias-Import (import std.math as m;) und qualifizierter Aufruf (std.math.Clamp64(…)) existieren beide nicht, undrestrictändert nichts.
Bis zur Klärung bleibt nur, eine der beiden Units zu meiden.
(Issue #1262)
Die Standardbibliothek liegt im std.-Namespace. Alle wichtigen Units:
// Basisfunktionalität
import std.io; // Print, PrintLn, ReadLine, ...
import std.string; // StrFind, StrReplace, StringBuilder, ...
import std.alloc; // malloc, free_mem, ...
import std.math; // Sin, Cos, Sqrt, Floor, ...
import std.result; // Result<T, E>
// Netzwerk
import std.net.socket; // TCP, UDP, Unix Sockets
import std.net.http; // HTTP/1.1 Client
import std.net.dns; // DNS-Auflösung
// Datenbanken
import std.db.sqlite; // SQLite3
import std.db.postgres; // PostgreSQL
import std.db.redis; // Redis
// PDF
import std.pdf; // PDF erstellen (High-Level-Einstiegspunkt)
// Crypto
import std.crypto.aes; // AES-Verschlüsselung
import std.hash; // Hashing
// System
import std.fs; // Dateisystem
import std.process; // Prozessverwaltung
import std.thread; // Threads
import std.time; // Zeitfunktionen
import std.env; // Kommandozeilenargumente
Alle verfügbaren Units: → Standard Library — vollständige Übersicht
Eine typische Projektstruktur mit mehreren Units:
my_project/
├── main.lyx → unit main;
├── config.lyx → unit config;
├── db/
│ ├── connection.lyx → unit db.connection;
│ └── queries.lyx → unit db.queries;
└── api/
├── handler.lyx → unit api.handler;
└── response.lyx → unit api.response;
// db/connection.lyx
unit db.connection;
import std.db.postgres;
pub con DB_HOST: pchar := "localhost";
pub con DB_PORT: int64 := 5432;
pub fn Connect(dbName: pchar): int64 {
var conn: int64 := PGConnect(DB_HOST, DB_PORT, dbName, "app", "secret");
if (conn == 0 || PGIsConnected(conn) == 0) { return 0; }
return conn;
}
// api/handler.lyx
unit api.handler;
import std.io;
import std.net.http;
import db.connection; // eigene Unit — relativer Pfad
pub fn HandleGetUsers(conn: int64): void {
// conn kommt aus db.connection.Connect(...)
var res: int64 := PGQuery(conn, "SELECT id, name FROM users");
// ...
}
// main.lyx
unit main;
import std.io;
import db.connection;
import api.handler;
pub fn main(): int64 {
var conn: int64 := Connect("myapp");
if (conn == 0) {
PrintLn("Datenbankverbindung fehlgeschlagen");
return 1;
}
HandleGetUsers(conn);
PGClose(conn);
return 0;
}
Der Compiler sucht Units in dieser Reihenfolge:
–include-path (lyxc –include-path=/my/libs …)std.*-Units)
# Einfacher Build
lyxc main.lyx -o my_app
# Mehrere Include-Pfade
lyxc main.lyx --include-path=./libs --include-path=/opt/lyx/contrib -o my_app
# Alle .lyx-Dateien angeben (bei kleinen Projekten)
lyxc main.lyx db/connection.lyx api/handler.lyx -o my_app
Neben .lyx-Quellen kann im Suchpfad eine vorkompilierte .lyu liegen (erzeugt mit lyxc --compile-unit quelle.lyx -o quelle.lyu). Für die Auflösung gilt eine einfache Regel:
.lyx. Liegen beide nebeneinander, gewinnt die Quelle — immer..lyu nur als Rückfall, wenn keine Quelle gefunden wurde.
lyxc main.lyx --trace-imports -o app
[import] mathe -> mathe.lyx # Quelle vorhanden: sie gewinnt
[import] mathe : mathe.lyx nicht gefunden, versuche .lyu # Rückfall
[import] mathe -> mathe.lyu
<WRAP center round important 90%>
Eine .lyu allein ist keine benutzbare Unit (#2014, nachgemessen mit lyxc 1.2.5C). Sie trägt eine Namensliste — Symbole mit Typ —, aber keinen Code, keine Struktur und keine Konstantenwerte.
Fehlt die Quelle, bricht die Übersetzung inzwischen deutlich ab:
error: codegen: Quelle der importierten Unit nicht lesbar: mathe.lyx
Das ist ein Fortschritt gegenüber dem früheren Verhalten (#1966), wo eine Konstante still 0 wurde. An der Sache ändert es nichts: wer Units weitergibt, gibt die .lyx mit. Die .lyu ist eine Bauprobe und eine Inhaltsangabe (--unit-info), kein Bibliotheksformat.
Seit #1971 trägt das Format immerhin eingebettete Ressourcen mit — siehe std.res. </WRAP>
Seit dem ersten Schritt zu #2014 sucht der Compiler .lyu-Dateien zusätzlich unter einer Architekturebene. Er baut aus seinem --target genau einen Pfad und probiert den — kein Durchprobieren, keine Rangfolge zwischen Architekturen und damit kein Fall, in dem er die falsche erwischt:
--target | gesuchter Pfad |
|---|---|
arm64 | arm64/std/io.lyu |
riscv | riscv64/std/io.lyu |
| ohne Angabe | x86_64/std/io.lyu |
Erst wenn kein Suchweg mit Architekturebene aufgeht, wird der bisherige Pfad ohne sie probiert. Die Ebene gilt für alle fünf Suchwege — relativ, Paketwurzeln, -I, --std-path und Installationspfad.
<WRAP center round important 90%>
Am Verhalten ändert das noch nichts. Die Ablage ist für den Fall vorbereitet, dass eine .lyu später Objektcode trägt; heute wird die Quelle weiterhin gebraucht. Legt man eine .lyu unter x86_64/ ab und entfernt die .lyx, findet der Compiler die Datei zwar, bricht aber im Codegen ab.
</WRAP>
Mehrere Prüfungen endeten früher an der Unit-Grenze und lieferten dort still 0 statt einer Meldung. Seit lyxc 1.1.0A/1.1.1C greifen sie auch bei importierten Units:
| Fall | Verhalten heute | Issue |
|---|---|---|
| Unbekanntes Struct-Feld einer importierten Unit | unknown field — Feld- und Methodennamen werden beim Import in eine Seitentabelle geschrieben | #1573 |
Doppelt definierte private fn in einer importierten Unit | wird gemeldet; vorher band der Aufruf an die erste Definition, die Argumentzahl blieb ungeprüft | #1567 |
| Struct-Parameter über Modulgrenzen | Typprüfung; int64 statt Struct wird abgewiesen | #1606 |
| Falsche Argumentzahl bei Methoden importierter Klassen | wird gemeldet | #1626 |
Klasse aus fremder Unit ohne new | wird abgewiesen (new oder null angeben) | #1570 |
| Zeichenketten-Parameter der Standardbibliothek | als pchar deklariert — der frühere Cast "text"c as int64 entfällt an 337 Aufrufstellen | #1613 |
| Unbekanntes Feld einer Klasse, deren Basisklasse aus einer anderen Unit stammt | unknown field — vorher still angenommen | #1666 |
Eine nicht-pub Funktion ist außerhalb ihrer Unit unsichtbar; ihr Name ist damit frei wählbar. Bis lyxc 1.1.2H stimmte das nicht: zwei importierte Units mit je einer privaten helper-Funktion teilten sich am Ende eine Definition, ausgewählt über die Reihenfolge der import-Zeilen. Die Stelligkeit wurde dabei nicht geprüft — fehlende Argumente kamen vom Stack (#1655). Bei pub wurde derselbe Fall immer schon korrekt als mehrdeutiges Symbol gemeldet.
// m/a.lyx
unit m.a;
import std.io;
fn helper(x: int64, y: int64, z: int64): void { PrintLn("A.helper"); }
pub fn CallA(): void { helper(1, 2, 3); }
// m/b.lyx
unit m.b;
import std.io;
fn helper(v: int64): void { PrintLn("B.helper"); }
pub fn CallB(): void { helper(42); }
Ein Programm, das beide Units importiert und CallA() sowie CallB() ruft, gibt seit 1.1.2J A.helper und B.helper aus — jede Unit ruft ihre eigene Fassung.
Praktische Folge: eine grüne Übersetzung sagt heute mehr aus als früher. Wer eine ältere Fassung eines Projekts erstmals mit 1.1.x übersetzt, bekommt deshalb unter Umständen neue Meldungen — sie zeigen Fehler, die vorher stumm blieben.
Empfehlungen für die Strukturierung von Lyx-Projekten:
| Situation | Empfehlung |
|---|---|
| Namenskollision zwischen Units | Präfix-Konvention einhalten (MyUnit*) |
| Globaler Zustand | Lieber pub fn Get() / pub fn Set() als pub var |
| Zyklische Abhängigkeit | Unit aufteilen — gemeinsame Typen in eine separate types-Unit |
| Große Units (> 500 Zeilen) | In Unterunits aufteilen: mylib.core, mylib.helpers, … |
| Init-Code beim Import | Nicht in Unit-Scope — stattdessen pub fn Init() explizit aufrufen |
| Interne Hilfsfunktionen | Privat lassen — nur was wirklich von außen gebraucht wird mit pub |
→ Vollständige Syntax-Referenz
→ Erste Schritte — erstes Programm
→ Compiler-Parameter
Letzte Aktualisierung: 2026-09-05 (#1966, gemessen mit lyxc 1.2.2B) — Abschnitt „Vorkompilierte Units (*.lyu) im Suchpfad„ ergänzt: die .lyx gewinnt immer, die .lyu ist nur Rückfall und trägt weder Code noch Konstantenwerte.
Vorherige letzte Aktualisierung: 2026-08-19 — private Namenskollision zwischen Units (#1655) und die Feldprüfung über Unit-Grenzen (#1666) ergänzt; Beispiele mit lyxc 1.1.3I übersetzt und ausgeführt.
Vorherige Aktualisierung: 2026-08-18 — Abschnitt „Was der Compiler über Unit-Grenzen prüft“ ergänzt (#1573, #1567, #1606, #1626, #1570, #1613).
Vorherige Aktualisierung: 2026-08-11
Codebeispiele geprüft: gegen lyxc 1.2.5C übersetzt (Prüflauf 2026-09-08 über die gesamte Doku: 574 Vollprogramme, 0 echte Fehler; zusätzlich 5159 Aufrufe gegen die pub fn-Signaturen in aurum/std gehalten, 0 Abweichungen).