Lyx – Generics & Interfaces

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.

 
trait und impl gibt es nicht. Frühere Fassungen dieser Seite beschrieben eine Rust-artige Syntax (trait Printable { … }, impl Printable for P { … }). Beides wird mit unexpected top-level token abgewiesen und steht auch nicht in der Grammatik. Die Schnittstelle heißt interface, die Zuordnung implements — beschrieben in Abschnitt 2.

OOP – Klassen und Vererbung · Funktionen · Datentypen


1. Generische Funktionen

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); }

  • Ein Typparameter gilt nur innerhalb seiner Funktion.
  • Mehrere Parameter werden mit Komma getrennt: fn Pair&lt;A, B&gt;.
  • Die eckige Schreibweise Id[T] gibt es nicht — der Parser erwartet spitze Klammern.
 
Generische Typen gibt es nicht. type Box&lt;T&gt; = struct { … } und die Klassenvariante werden mit unexpected top-level token abgewiesen. Generisch sind allein Funktionen. Für einen typunabhängigen Container bleibt der Weg über int64-Adressen (→ Rohspeicher).

2. Interfaces

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.

Als Parametertyp

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).

Mehrere Interfaces

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; }
};

Was Interfaces nicht können

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

3. Typbedingungen

 
Schranken an Typparametern haben keine Wirkung. Die Grammatik führt in TypeParamList nur Bezeichner — eine Form fn Max&lt;T: Vergleichbar&gt;(…) ist dort nicht vorgesehen. Der Parser nimmt sie zwar an, aber ein Methodenaufruf auf dem Typparameter scheitert mit undefined function.

Wer eine Schnittstelle erzwingen will, nimmt den Interface-Typ als Parametertyp (Abschnitt 2) — das prüft der Compiler zuverlässig.

4. Monomorphisierung

Für jeden verwendeten Typ entsteht eine eigene Fassung der Funktion. Das hat zwei Folgen:

  • Kein Laufzeit-Overhead. Der Aufruf ist ein direkter Sprung; es gibt keine Typprüfung zur Laufzeit und keine Indirektion über eine Tabelle.
  • Code wächst mit der Zahl der Typen. Eine generische Funktion, die mit fünf Typen aufgerufen wird, liegt fünfmal im Binary.

Für sicherheitskritischen Code ist das die erwünschte Eigenschaft: Die WCET einer monomorphisierten Funktion ist exakt berechenbar, weil der Zieltyp zur Übersetzungszeit feststeht.


5. Statischer und dynamischer Dispatch

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.


6. Generics im Safety-Umfeld

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).