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.
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
}
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
}
}
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;
}
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 };
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) |
trait UART). So kann der Applikationscode generisch gegen den Trait geschrieben und für verschiedene Hardware-Backends (ESP32, RISC-V) spezialisiert werden.