====== 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. → [[lyx_-_programmiersprache:sprache:datentypen|Datentypen]] · [[lyx_-_programmiersprache:sprache:enums|Enums]] · [[lyx_-_programmiersprache:sprache:oop|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 [[lyx_-_programmiersprache:sprache:datentypen|Datentypen]] und [[lyx_-_programmiersprache:sprache:oop|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 (''int8'' bis ''int64'', ''uint8'' bis ''uint64''). 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 vorherigen ''is''-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 | → [[lyx_-_programmiersprache:sprache:datentypen|Datentypen — vollständige Typübersicht]]\\ → [[lyx_-_programmiersprache:sprache:enums|Enums — Aufzählungstypen]]\\ → [[lyx_-_programmiersprache:sprache:oop|OOP — Klassen, Vererbung, Interfaces]]\\ → [[lyx_-_programmiersprache:guides:do-178c|DO-178C — Casts in sicherheitskritischem Code]] Letzte Aktualisierung: 2026-06-05