Lyx – Generics & Traits

Generics ermöglichen es, Funktionen und Strukturen zu schreiben, die mit verschiedenen Datentypen arbeiten, ohne den Code mehrfach kopieren zu müssen. Traits definieren dabei die Anforderungen (Interfaces), die diese Typen erfüllen müssen.

1. Generische Funktionen

Eine generische Funktion verwendet Typparameter in spitzen Klammern <T>.

fn swap<T>(var a: T, var b: T) {
    let temp := a;
    a := b;
    b := temp;
}
 
fn main() {
    var x := 10;
    var y := 20;
    swap<int>(x, y); // T wird zu int
}
  • Monomorphisierung: Der Compiler erzeugt für jede genutzte Typ-Kombination eine eigene, optimierte Version der Funktion im Maschinencode.

2. Traits (Schnittstellen)

Traits definieren ein Set von Methoden, die ein Typ implementieren muss. Sie dienen als „Constraints“ (Einschränkungen) für Generics.

trait Drawable {
    fn draw();
}
 
type Circle = struct { radius: f32 }
 
// Implementierung des Traits für Circle
impl Drawable for Circle {
    fn draw() {
        // Logik zum Zeichnen
    }
}

3. Generic Constraints

Mithilfe von Traits kann man einschränken, welche Typen für ein Generic zulässig sind. Dies ist essenziell, um sicherzustellen, dass benötigte Operationen (wie Addition oder Vergleiche) auf dem Typ T existieren.

// T muss das Trait 'Comparable' erfüllen
fn get_max<T: Comparable>(a: T, b: T): T {
    if (a > b) {
        return a;
    }
    return b;
}

4. Generische Strukturen & Klassen

Auch komplexe Datentypen können generisch definiert werden.

type Box<T> = struct {
    content: T,
    is_empty: bool
}
 
var int_box: Box<int> := Box { content: 42, is_empty: false };

5. Statische vs. Dynamische Dispach

Lyx bevorzugt aus Performance-Gründen die statische Auflösung.

Merkmal Statische Generics (Standard) Dynamische Traits (v0.9.5+)
Mechanismus Code-Duplizierung (Monomorph) Virtual Table (V-Table)
Performance Maximal (Inlining möglich) Overhead durch indirekten Sprung
Binärgröße Steigt pro Typ-Instanz Bleibt konstant
Zertifizierung Exzellent (WCET berechenbar) Schwierig (indirekte Sprünge)

6. Best Practices im Safety-Umfeld

  • Vermeide tiefe Generic-Hierarchien: In DAL-A Systemen sollte die Code-Komplexität überschaubar bleiben, um die Testbarkeit zu gewährleisten.
  • Traits für Hardware-Abstraktion: Nutze Traits, um Treiber-Schnittstellen zu definieren (z.B. trait UART). So kann der Applikationscode generisch gegen den Trait geschrieben und für verschiedene Hardware-Backends (ESP32, RISC-V) spezialisiert werden.
  • Constraints nutzen: Verwende immer explizite Constraints, damit Fehlermeldungen bereits beim Aufruf der generischen Funktion entstehen und nicht erst tief im Compiler-Backend.