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.
