Typ-Aliase und Typumwandlung
Lyx hat ein explizites Typsystem: Implizite Konvertierungen gibt es nicht. Jede Typumwandlung muss mit dem as-Operator geschrieben werden. Typ-Aliase über type benennen vorhandene Typen neu — entweder als einfaches Alias, als Range-Typ mit Wertebereichseinschränkung oder als neuen Struct-/Class-Typ.
→ Datentypen · Enums · OOP — Klassen und Vererbung
1. Typ-Aliase mit type
type deklariert einen neuen Namen für einen vorhandenen Typ oder definiert einen zusammengesetzten Typ:
Einfacher Alias für Primitivtypen
// Einfache Aliase — kein neuer Typ, nur ein anderer Name
type Byte = uint8;
type Word = uint16;
type DWord = uint32;
type QWord = int64;
type Fd = int64; // Dateideskriptor
type Handle = int64; // Generischer Ressource-Handle
type Ptr = int64; // Roher Zeiger als int64
// Verwendung: exakt wie der Basistyp
var fd: Fd := open("/etc/hosts", 0, 0);
var h: Handle := SQLiteOpen("/data/app.db");
Einfache Aliase sind strukturell äquivalent zum Basistyp: Eine Funktion, die int64 erwartet, akzeptiert auch Fd — der Compiler betrachtet sie als denselben Typ.
Aliase für Lesbarkeit
type Celsius = f64;
type Kelvin = f64;
type Millibar = f64;
fn CelsiusToKelvin(temp: Celsius): Kelvin {
return temp + 273.15;
}
fn main(): int64 {
var t: Celsius := 20.0;
var k: Kelvin := CelsiusToKelvin(t);
// Hinweis: Celsius und Kelvin sind beide f64 — der Compiler unterscheidet sie NICHT
// Für echte Typsicherheit → Range-Typen (Abschnitt 2)
PrintF64(k);
return 0;
}
Aliase für Structs und Klassen
Die häufigste Form von type: Neue Struct- und Klassentypen (ausführlich in Datentypen und OOP behandelt):
type Point = struct {
x: f64;
y: f64;
fn Distance(other: Point): f64 {
var dx: f64 := self.x - other.x;
var dy: f64 := self.y - other.y;
return Sqrt(dx * dx + dy * dy);
}
};
type Vector2 = struct { x: f64; y: f64; }; // Kompaktform ohne Methoden
2. Range-Typen
Range-Typen schränken den gültigen Wertebereich eines Integer-Typs ein. Der Compiler prüft Literale zur Compile-Zeit; Laufzeit-Verstöße erzeugen einen Panic.
type Altitude = int64 range -1000..60000; // Meter über NN
type Speed = int64 range 0..300; // km/h
type DalLevel = int8 range 1..5;
type Temp = int32 range -273..1000; // Grad Celsius
type Percent = uint8 range 0..100;
type Port = uint16 range 1..65535;
Compile-Zeit-Prüfung
var alt: Altitude := 70000; // ✗ Compiler-Fehler: 70000 liegt außerhalb -1000..60000
var spd: Speed := -10; // ✗ Compiler-Fehler: -10 liegt außerhalb 0..300
var pct: Percent := 101; // ✗ Compiler-Fehler: 101 > 100
Laufzeit-Prüfung bei Zuweisung
fn SetSpeed(raw: int64): Speed {
// raw kommt von einem Sensor — Wert zur Compile-Zeit unbekannt
if (raw < 0 || raw > 300) {
PrintLn("Ungültiger Geschwindigkeitswert");
return 0 as Speed; // Fallback
}
return raw as Speed; // Sicher: Wert wurde manuell geprüft
}
Range-Typen in DO-178C
Range-Typen sind in sicherheitskritischem Code ein bevorzugtes Mittel zur physikalischen Plausibilitätsprüfung:
unit flight_control;
type BankAngle = int32 range -60..60; // Grad (physikalisch begrenzt)
type PitchAngle = int32 range -30..30; // Grad
type Mach = f64; // kein Range auf f64 — nur int-Typen
@flight_crit(DAL-A)
fn ComputeAileron(bank: BankAngle, target: BankAngle): int32 {
// bank und target sind garantiert in [-60, 60]
return (target - bank) as int32;
}
Range-Typen gibt es nur für Integer-Typen (int8bisint64,uint8bisuint64). Für Fließkomma-Bereiche muss manuell geprüft werden.
3. Der as-Operator
Alle Typumwandlungen sind explizit mit as. Keine impliziten Promotionen, kein C-Style-Casting durch Klammern.
Zwischen Integer-Typen
var big: int64 := 100000;
var small: int32 := big as int32; // Passt — 100000 liegt in int32-Bereich
var byte_v: uint8 := big as uint8; // Truncation: 100000 mod 256 = 160
var u: uint64 := -1 as uint64; // Bit-Reinterpretation: 2⁶⁴−1
// Vorzeichen-Erweiterung (sign-extend) beim Verbreitern
var s8: int8 := -10;
var s64: int64 := s8 as int64; // -10 (korrekt sign-extended)
// Vorsicht: uint → int bei großen Werten
var u32: uint32 := 0xFFFFFFFF;
var i32: int32 := u32 as int32; // -1 (Bit-Reinterpretation)
Integer ↔ Float
var i: int64 := 42;
var f: f64 := i as f64; // 42.0 (exakt bis ±2⁵³)
var pi: f64 := 3.14159;
var n: int64 := pi as int64; // 3 (truncation Richtung Null, kein Runden)
var neg: f64 := -2.9;
var m: int64 := neg as int64; // -2 (floor toward zero, nicht -3)
Integer ↔ Pointer
// MMIO-Register adressieren
var addr: int64 := 0x40020000;
var reg: ^uint32 := addr as ^uint32; // Integer → Pointer (MMIO)
var back: int64 := reg as int64; // Pointer → Integer
// Vorsicht: nur in unsafe-Blöcken oder bei MMIO-Code
unsafe {
var gpio: ^uint32 := 0x3FF44004 as ^uint32;
gpio^ := gpio^ | (1 << 5); // Bit 5 setzen
}
Enum ↔ Integer
enum State { Idle = 0, Running = 1, Error = 2 }
var s: State := State::Running;
var n: int64 := s as int64; // 1
// int64 → Enum: kein Werte-Check — Verantwortung liegt beim Programmierer
var raw: int64 := 99;
var bad: State := raw as State; // Undefined Behavior: 99 ist kein gültiger State
Konvertierungs-Regeln im Überblick
Alle Regeln für den as-Operator:
| Von → Nach | Verhalten | Verlust? |
|---|---|---|
int8..64 → breiter intN | Sign-extend (Vorzeichen erhalten) | Nein |
int8..64 → schmäler intN | Truncation (modular, niedrige Bits) | Möglich |
intN → uintN gleicher Breite | Bit-Reinterpretation | Semantisch |
uintN → intN gleicher Breite | Bit-Reinterpretation | Semantisch |
intN → f64 | Exakte Konvertierung (bis ±2⁵³) | Bei großen int64 |
f64 → intN | Truncation Richtung Null | Nachkommastellen |
f32 → f64 | Präzisions-Erweiterung | Nein |
f64 → f32 | Rounding to nearest, ggf. Inf | Ja |
Pointer → int64 | Adress-Wert | Nein |
int64 → Pointer | Integer als Adresse | Unsafe |
Enum → int64 | Numerischer Wert | Nein |
int64 → Enum | Kein Werte-Check | Unsafe |
4. Der is-Operator
is prüft den Laufzeit-Typ eines Objekts. Nur für Klassen mit Vererbung (class extends) sinnvoll — bei Werttypen und Enums ist der Typ immer statisch bekannt.
type Shape = class { virtual fn Area(): f64; };
type Circle = class extends Shape { pub radius: f64; };
type Rect = class extends Shape { pub w: f64; pub h: f64; };
fn PrintShape(s: Shape): void {
if (s is Circle) {
var c: Circle := s as Circle; // Downcast: sicher nach is-Prüfung
Print("Kreis, Radius: ");
PrintF64(c.radius);
} else if (s is Rect) {
var r: Rect := s as Rect;
Print("Rechteck: ");
PrintF64(r.w);
Print(" × ");
PrintF64(r.h);
}
PrintLn("");
}
fn main(): int64 {
var c: Circle := new Circle();
c.radius := 5.0;
PrintShape(c); // Kreis, Radius: 5.0
var r: Rect := new Rect();
r.w := 3.0; r.h := 4.0;
PrintShape(r); // Rechteck: 3.0 × 4.0
dispose c;
dispose r;
return 0;
}
is ohne vorherigen Check — Vorsicht
Ein Downcast mit as ohne is-Prüfung ist möglich, aber undefiniert wenn der Laufzeittyp nicht passt:
fn UnsafeDowncast(s: Shape): void {
var c: Circle := s as Circle; // ⚠ Kein is-Check — Crash wenn s ein Rect ist
PrintF64(c.radius);
}
In@dal(A)-Code ist ein Downcast ohne vorherigenis-Check ein Compiler-Fehler.
5. Typkonvertierung vs. Konvertierungsfunktionen
as ist ein reiner Bit- oder Interpretations-Cast — es wird keine Wertprüfung durchgeführt. Für inhaltlich korrekte Konvertierungen gibt es Standardfunktionen:
import std.string;
import std.conv;
// Zahlen in Strings (und umgekehrt) — nicht mit as!
var n: int64 := 42;
var s: pchar := IntToStr(n); // "42"
var f: f64 := 3.14;
var fs: pchar := F64ToStr(f, 2); // "3.14"
var parsed: int64 := StrToInt("123"); // 123
var fparsed: f64 := StrToF64("2.5"); // 2.5
// Falsch — as konvertiert nicht Text zu Zahl:
var wrong: int64 := "42" as int64; // Pointer-Wert der Zeichenkette, NICHT 42
| Aufgabe | Mittel |
|---|---|
| Primitiver Typ → anderen Primitiv-Typ | as |
| Integer → Float (exakt) | as |
| Float → Integer (truncation) | as |
| Zahl → Zeichenkette | IntToStr, F64ToStr (std.conv) |
| Zeichenkette → Zahl | StrToInt, StrToF64 (std.conv) |
| Klasse → Basisklasse | as (Upcast, immer sicher) |
| Basisklasse → Unterklasse | is prüfen, dann as (Downcast) |
| Enum → int64 | as |
| int64 → Enum | Nur wenn Wert zuvor geprüft (via match oder manuell) |
6. Linter-Warnungen bei riskanten Casts
–lint aktiviert Warnungen für Casts, die Datenverlust verursachen können:
lyxc main.lyx --lint -o app
warning: narrowing cast int64 → uint8 may truncate
--> main.lyx:42:18
note: value 1000 truncated to 232
warning: float-to-int cast f64 → int32 discards fractional part
--> main.lyx:57:22
In @dal(A)-Modulen werden diese Warnungen automatisch zu Fehlern — Datenverlust durch Cast ist nicht zertifizierbar:
@dal(A)
fn ReadSensor(): int32 {
var raw: int64 := GetRawValue();
return raw as int32; // ERROR in @dal(A): narrowing cast ohne explizite Prüfung
// Korrekt:
// if (raw < -2147483648 || raw > 2147483647) { Panic(); }
// return raw as int32;
}
7. Zusammenfassung
| Konzept | Syntax | Anmerkung |
|---|---|---|
| Einfacher Alias | type X = Y | Strukturell identisch mit Y |
| Range-Typ | type X = intN range a..b | Compile-Zeit-Check für Literale |
| Struct-Typ | type X = struct { … } | Wertetyp, Stack-allokiert |
| Klassen-Typ | type X = class { … } | Referenztyp, Heap-allokiert |
| Typumwandlung | val as TargetType | Immer explizit; kein implizites Casting |
| Laufzeit-Typrüfung | obj is TypeName | Nur für Klassen mit Vererbung |
| Riskanter Cast | –lint warnt | In @dal(A) → Fehler |
→ Datentypen — vollständige Typübersicht
→ Enums — Aufzählungstypen
→ OOP — Klassen, Vererbung, Interfaces
→ DO-178C — Casts in sicherheitskritischem Code
Letzte Aktualisierung: 2026-06-05
