Lyx kennt zwei Formen der Abstraktion über Typen: generische Funktionen, die für jeden eingesetzten Typ eigenen Code erzeugen (Monomorphisierung), und Interfaces, die eine gemeinsame Schnittstelle für verschiedene Klassen festlegen.
traitundimplgibt es nicht. Frühere Fassungen dieser Seite beschrieben eine Rust-artige Syntax (trait Printable { … },impl Printable for P { … }). Beides wird mitunexpected top-level tokenabgewiesen und steht auch nicht in der Grammatik. Die Schnittstelle heißtinterface, die Zuordnungimplements— beschrieben in Abschnitt 2.
→ OOP – Klassen und Vererbung · Funktionen · Datentypen
Ein Typparameter steht in spitzen Klammern hinter dem Funktionsnamen. Der Compiler erzeugt für jeden tatsächlich verwendeten Typ eine eigene Fassung — der Aufruf ist damit ein gewöhnlicher, direkter Sprung ohne Laufzeit-Auflösung.
import std.io;
fn Id<T>(x: T): T { return x; }
fn Pair<A, B>(a: A, b: B): A { return a; }
fn main(): int64 {
PrintLn(IntToStr(Id(42))); // 42
PrintLn(IntToStr(Pair(7, 9))); // 7
return 0;
}
Der Typ lässt sich auch ausdrücklich angeben, etwa für Structs:
type P = struct { x: int64; };
fn main(): int64 {
var p: P;
p.x := 5;
var q: P := Id<P>(p);
PrintLn(IntToStr(q.x)); // 5
return 0;
}
Ein Typparameter darf innerhalb der Funktion an einen weiteren generischen Aufruf weitergereicht werden:
fn Zweimal<T>(x: T): T { return Id<T>(x); }
fn Pair<A, B>.Id[T] gibt es nicht — der Parser erwartet spitze Klammern.Generische Typen gibt es nicht.type Box<T> = struct { … }und die Klassenvariante werden mitunexpected top-level tokenabgewiesen. Generisch sind allein Funktionen. Für einen typunabhängigen Container bleibt der Weg überint64-Adressen (→ Rohspeicher).
Ein interface listet Methodensignaturen — jede endet mit einem Semikolon, ohne Rumpf. Eine Klasse verpflichtet sich mit implements darauf.
import std.io;
type Zeichenbar = interface {
fn Flaeche(): int64;
};
type Kreis = class implements Zeichenbar {
r: int64;
fn Flaeche(): int64 { return self.r * self.r; }
};
fn main(): int64 {
var k: Kreis := new Kreis();
k.r := 4;
PrintLn(IntToStr(k.Flaeche())); // 16
return 0;
}
Die Verpflichtung wird geprüft: Fehlt eine Methode, meldet der Compiler class missing interface method. Das ist der eigentliche Gewinn — die Schnittstelle ist nicht bloß Dokumentation.
Der Interface-Name taugt als Typ. Damit lässt sich eine Funktion schreiben, die jede implementierende Klasse annimmt:
unit main;
import std.io;
import std.string;
type Zeichenbar = interface {
fn Flaeche(): int64;
};
type Kreis = class implements Zeichenbar {
r: int64;
fn Flaeche(): int64 { return self.r * self.r; }
};
type Quadrat = class implements Zeichenbar {
s: int64;
fn Flaeche(): int64 { return self.s * self.s; }
};
fn Zeige(z: Zeichenbar): int64 { return z.Flaeche(); }
fn main(): int64 {
var k: Kreis := new Kreis(); k.r := 3;
var q: Quadrat := new Quadrat(); q.s := 5;
PrintLn(IntToStr(Zeige(k))); // 9
PrintLn(IntToStr(Zeige(q))); // 25
return 0;
}
9
25
Der Aufruf über den Interface-Typ trifft die richtige Implementierung — das war bis lyxc 1.0.14 nicht der Fall (Issue #1133).
type Lesbar = interface { fn Lies(): int64; };
type Schreibbar = interface { fn Schreib(v: int64): int64; };
type Datei = class implements Lesbar, Schreibbar {
wert: int64;
fn Lies(): int64 { return self.wert; }
fn Schreib(v: int64): int64 { self.wert := v; return 0; }
};
| Konstrukt | Verhalten |
|---|---|
type S = struct implements I | abgewiesen — nur Klassen können implements tragen |
type B = interface extends A | abgewiesen — keine Interface-Vererbung |
| Methodenrumpf im Interface | die Klasse muss die Methode dennoch selbst mitbringen; eine Standardimplementierung entsteht daraus nicht |
Schranken an Typparametern haben keine Wirkung. Die Grammatik führt inTypeParamListnur Bezeichner — eine Formfn Max<T: Vergleichbar>(…)ist dort nicht vorgesehen. Der Parser nimmt sie zwar an, aber ein Methodenaufruf auf dem Typparameter scheitert mitundefined function.
Wer eine Schnittstelle erzwingen will, nimmt den Interface-Typ als Parametertyp (Abschnitt 2) — das prüft der Compiler zuverlässig.
Für jeden verwendeten Typ entsteht eine eigene Fassung der Funktion. Das hat zwei Folgen:
Für sicherheitskritischen Code ist das die erwünschte Eigenschaft: Die WCET einer monomorphisierten Funktion ist exakt berechenbar, weil der Zieltyp zur Übersetzungszeit feststeht.
| Weg | Auflösung | WCET | Einsatz |
|---|---|---|---|
| Generische Funktion | Übersetzungszeit (Monomorphisierung) | exakt | Algorithmen über mehrere Typen |
| Interface als Parametertyp | Laufzeit | eingeschränkt | Austauschbare Komponenten, Treiber |
Virtuelle Methode (virtual/override) | Laufzeit (V-Table) | eingeschränkt | Klassenhierarchien → OOP |
In @flight_crit-Funktionen (Attributes & Pragmas) ist der generische Weg vorzuziehen: Er erzeugt keinen indirekten Sprung, dessen Ziel erst zur Laufzeit feststeht.
| Situation | Empfehlung |
|---|---|
| DAL-A/B-Regelzyklus | Generische Funktion — monomorphisiert, WCET exakt |
| Treiber, austauschbare Komponenten | Interface als Parametertyp |
| Container über mehrere Typen | Es gibt keine generischen Typen; feste Typen oder int64-Adressen |
| Schnittstelle erzwingen | implements — der Compiler prüft die Vollständigkeit |
Weiterführende Seiten:
Letzte Aktualisierung: 2026-08-09
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).