Inhaltsverzeichnis

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 (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
intNuintN gleicher Breite Bit-Reinterpretation Semantisch
uintNintN gleicher Breite Bit-Reinterpretation Semantisch
intNf64 Exakte Konvertierung (bis ±2⁵³) Bei großen int64
f64intN Truncation Richtung Null Nachkommastellen
f32f64 Präzisions-Erweiterung Nein
f64f32 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

Datentypen — vollständige Typübersicht
Enums — Aufzählungstypen
OOP — Klassen, Vererbung, Interfaces
DO-178C — Casts in sicherheitskritischem Code

Letzte Aktualisierung: 2026-06-05