Lyx – Pattern Matching

match ist das zentrale Kontrollfluss-Element für Fallunterscheidungen in Lyx. Es ist typsicher, exhaustiv — der Compiler prüft, ob alle möglichen Werte abgedeckt sind — und wird vom Backend oft zu einer Jump-Table optimiert, die schneller ist als eine if/else-Kette.

Der Unterschied zu switch in C: Es gibt kein Fall-through, kein vergessenes break, keine Sprünge zwischen Armen. Jeder Arm ist vollständig isoliert. Der Compiler warnt, wenn Fälle fehlen.

Fehlerbehandlung · Bedingungen · Datentypen


1. Grundsyntax

match (ausdruck) {
    case muster1 => anweisung;
    case muster2 => { anweisung1; anweisung2; }
    default      => anweisung;
}

  • Jeder Arm besteht aus case muster ⇒ gefolgt von einer Anweisung oder einem Block.
  • default fängt alle Werte ab, die kein explizites case getroffen haben.
  • Kein Fall-through — nur der erste passende Arm wird ausgeführt.
  • Blöcke { } sind optional bei einer einzelnen Anweisung, aber empfohlen.

fn HttpStatusText(code: int64): pchar {
    match (code) {
        case 200 => return "OK";
        case 201 => return "Created";
        case 204 => return "No Content";
        case 301 => return "Moved Permanently";
        case 400 => return "Bad Request";
        case 401 => return "Unauthorized";
        case 403 => return "Forbidden";
        case 404 => return "Not Found";
        case 500 => return "Internal Server Error";
        case 503 => return "Service Unavailable";
        default  => return "Unknown";
    }
}

fn main(): int64 {
    PrintStr(HttpStatusText(404));   // "Not Found"
    PrintStr("\n");
    PrintStr(HttpStatusText(200));   // "OK"
    PrintStr("\n");
    return 0;
}


2. OR-Muster ( | )

Mehrere Werte können in einem einzigen Arm zusammengefasst werden:

fn ClassifyChar(c: int64): pchar {
    match (c) {
        case 65 | 69 | 73 | 79 | 85 => return "Vokal (groß)";
        case 97 | 101 | 105 | 111 | 117 => return "Vokal (klein)";
        case 48 | 49 | 50 | 51 | 52 |
             53 | 54 | 55 | 56 | 57  => return "Ziffer";
        case 32 | 9 | 10 | 13       => return "Whitespace";
        default                      => return "Sonstiges";
    }
}

fn ClassifyHttpMethod(method: int64): int64 {
    match (method) {
        case HTTP_GET | HTTP_HEAD | HTTP_OPTIONS => return METHOD_SAFE;
        case HTTP_POST | HTTP_PUT | HTTP_PATCH   => return METHOD_MUTATING;
        case HTTP_DELETE                         => return METHOD_DESTRUCTIVE;
        default                                  => return METHOD_UNKNOWN;
    }
}


3. Bereichsmuster ( .. )

Wertebereiche werden mit von..bis angegeben — der Bereich ist auf beiden Seiten einschließend:

fn AltitudeMode(altitude_m: int64): pchar {
    match (altitude_m) {
        case 0..500       => return "Bodennaher Betrieb";
        case 501..3000    => return "Platzrunde / Anflug";
        case 3001..10000  => return "Steigflug";
        case 10001..13000 => return "Reiseflug";
        case 13001..      => return "Hochflug";
        default           => return "Ungültige Höhe";
    }
}

fn HttpStatusClass(code: int64): pchar {
    match (code) {
        case 100..199 => return "Informational";
        case 200..299 => return "Success";
        case 300..399 => return "Redirection";
        case 400..499 => return "Client Error";
        case 500..599 => return "Server Error";
        default       => return "Unknown";
    }
}

fn main(): int64 {
    PrintStr(AltitudeMode(11500));       // "Reiseflug"
    PrintStr("\n");
    PrintStr(HttpStatusClass(404));      // "Client Error"
    PrintStr("\n");
    return 0;
}

OR-Muster und Bereiche können kombiniert werden:

fn IsSpecialPort(port: int64): bool {
    match (port) {
        case 80 | 443             => return true;    // HTTP(S)
        case 20 | 21              => return true;    // FTP
        case 22                   => return true;    // SSH
        case 1..1023              => return true;    // Privilegierte Ports
        default                   => return false;
    }
}


4. Enum-Matching und Exhaustivität

Das stärkste Argument für match in Lyx: Bei Enums prüft der Compiler, ob alle Werte abgedeckt sind. Fehlt ein Arm, ist es ein Compiler-Fehler — kein Warning, kein Silent-Fail.

enum FlightPhase {
    Preflight,
    Taxiing,
    Takeoff,
    Climbing,
    Cruise,
    Descending,
    Approach,
    Landing,
    Rollout
}

fn PhaseActions(phase: FlightPhase): void {
    match (phase) {
        case FlightPhase::Preflight   => { RunChecklist(); }
        case FlightPhase::Taxiing     => { EnableTaxiLights(); CheckFlaps(); }
        case FlightPhase::Takeoff     => { SetTakeoffPower(); RetractFlaps(); }
        case FlightPhase::Climbing    => { RetractGear(); SetClimbPower(); }
        case FlightPhase::Cruise      => { SetCruisePower(); EngageAutopilot(); }
        case FlightPhase::Descending  => { SetDescentRate(); }
        case FlightPhase::Approach    => { ExtendFlaps(); ArmAutobrake(); }
        case FlightPhase::Landing     => { ExtendGear(); SetLandingPower(); }
        case FlightPhase::Rollout     => { ApplyBrakes(); ReverseThrust(); }
        // Kein 'default' nötig — alle 9 Werte sind abgedeckt
        // Wird ein neuer Wert zum Enum hinzugefügt → Compiler-Fehler hier
    }
}

Wird FlightPhase::GoAround später hinzugefügt, erzeugt der Compiler sofort:

error: non-exhaustive match — FlightPhase::GoAround not covered
  --> flight_ctrl.lyx:42:5
  hint: add 'case FlightPhase::GoAround =>' or 'default =>'

Das ist eine Sicherheitsgarantie: Neue Zustände können nicht unbehandelt durchrutschen.

Enum mit Werten

enum SensorStatus {
    Ok        = 0,
    Timeout   = 1,
    OutOfRange = 2,
    HardwareFault = 3
}

fn HandleSensorStatus(s: SensorStatus): void {
    match (s) {
        case SensorStatus::Ok           => { /* normal */ }
        case SensorStatus::Timeout      => {
            PrintStr("Sensor-Timeout — Wiederholungsversuch\n");
            RetrySensor();
        }
        case SensorStatus::OutOfRange   => {
            PrintStr("Messwert außerhalb Plausibilitätsbereich\n");
            FlagSensorData();
        }
        case SensorStatus::HardwareFault => {
            PrintStr("Hardware-Defekt — Fallback aktivieren\n");
            ActivateRedundantSensor();
        }
    }
}


5. Match als Ausdruck

match kann direkt als Ausdruck verwendet werden — der Wert des passenden Arms wird zurückgegeben:

fn DayName(day: int64): pchar {
    return match (day) {
        case 1 => "Montag";
        case 2 => "Dienstag";
        case 3 => "Mittwoch";
        case 4 => "Donnerstag";
        case 5 => "Freitag";
        case 6 => "Samstag";
        case 7 => "Sonntag";
        default => "Ungültig";
    };
}

fn DaysInMonth(month: int64, year: int64): int64 {
    return match (month) {
        case 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31;
        case 4 | 6 | 9 | 11              => 30;
        case 2 => IsLeapYear(year) ? 29 : 28;
        default => 0;
    };
}

fn main(): int64 {
    PrintStr(DayName(3));                    // Mittwoch
    PrintStr("\n");
    PrintInt(DaysInMonth(2, 2024));          // 29
    PrintStr("\n");
    return 0;
}


6. Guard-Bedingungen

Mit when kann einem Muster eine zusätzliche Bedingung hinzugefügt werden. Der Arm passt nur, wenn sowohl das Muster als auch die Guard-Bedingung wahr sind:

fn ClassifyTemperature(sensor_id: int64, temp: f64): pchar {
    match (sensor_id) {
        case 1 when temp > 100.0  => return "Sensor 1: Überhitzung";
        case 1 when temp < -20.0  => return "Sensor 1: Untertemperatur";
        case 1                    => return "Sensor 1: Normal";
        case 2 when temp > 80.0   => return "Sensor 2: Warnung";
        case 2                    => return "Sensor 2: Normal";
        default                   => return "Unbekannter Sensor";
    }
}

fn RoutePacket(port: int64, size: int64): pchar {
    match (port) {
        case 80  when size > 65535 => return "HTTP: Paket zu groß";
        case 80                   => return "HTTP-Handler";
        case 443 when size > 65535 => return "HTTPS: Paket zu groß";
        case 443                  => return "HTTPS-Handler";
        case 1..1023              => return "Privilegierter Port";
        default                   => return "Unprivilegierter Port";
    }
}


7. Struct-Deconstruction

Match kann direkt in Struct-Felder hineinschauen und auf deren Werte matchen:

type Packet = struct {
    type_id:  int64;
    length:   int64;
    flags:    int64;
    checksum: int64;
};

fn ProcessPacket(pkt: Packet): void {
    match (pkt) {
        case Packet { type_id: 1, flags: 0 } => {
            PrintStr("Datenpunkt, keine Flags\n");
            ProcessData(pkt);
        }
        case Packet { type_id: 1 } => {
            PrintStr("Datenpunkt mit Flags\n");
            ProcessDataWithFlags(pkt);
        }
        case Packet { type_id: 2, length: 0..8 } => {
            PrintStr("Kurzbefehl\n");
            ProcessShortCommand(pkt);
        }
        case Packet { type_id: 2 } => {
            PrintStr("Langer Befehl\n");
            ProcessCommand(pkt);
        }
        case Packet { type_id: 255 } => {
            PrintStr("Heartbeat\n");
            ResetWatchdog();
        }
        default => {
            PrintStr("Unbekanntes Paket — verworfen\n");
        }
    }
}

Felder, die nicht im Muster auftauchen, werden ignoriert — es ist kein Wildcard nötig.


8. Verschachteltes Matching

match kann in anderen match-Armen verschachtelt werden — nützlich für mehrstufige Zustandsmaschinen:

enum ProtocolState { Idle, Handshake, Active, Closing }
enum ErrorCode     { None, Timeout, AuthFailed, Overflow }

fn HandleProtocolEvent(state: ProtocolState, error: ErrorCode): void {
    match (state) {
        case ProtocolState::Idle => {
            match (error) {
                case ErrorCode::None    => StartHandshake();
                case ErrorCode::Timeout => ResetConnection();
                default                 => LogError(error);
            }
        }
        case ProtocolState::Handshake => {
            match (error) {
                case ErrorCode::None        => TransitionToActive();
                case ErrorCode::AuthFailed  => RejectConnection();
                case ErrorCode::Timeout     => RetryHandshake();
                default                     => AbortHandshake();
            }
        }
        case ProtocolState::Active => {
            match (error) {
                case ErrorCode::None     => { /* normal */ }
                case ErrorCode::Overflow => HandleOverflow();
                default                  => GracefulClose();
            }
        }
        case ProtocolState::Closing => {
            // Im Closing-Zustand alle Fehler ignorieren
        }
    }
}


9. Performance

Der Compiler optimiert match je nach Struktur der Muster:

Muster-Struktur Backend-Strategie Eigenschaft
Wenige dichte Einzelwerte (0,1,2,3) Jump-Table O(1) — schnellste Variante
Viele verstreute Einzelwerte Binäre Suche O(log n)
Bereiche Vergleichsfolge O(n) im Worst Case
Struct-Deconstruction Feldvergleiche Abhängig von Feldanzahl
Enum (vollständig) Jump-Table oder direkt O(1) — Enum-Werte sind int64

# Assembler-Ausgabe prüfen — zeigt ob Jump-Table erzeugt wurde
lyxc main.lyx --emit-asm | grep -A 20 "HttpStatusText"

Ein match auf 10 dichte HTTP-Statuscodes erzeugt eine Jump-Table mit einem einzigen indirekten Sprung — ein if/else-Äquivalent würde 10 Vergleiche brauchen.


10. match vs. if/else — Wann was

Kriterium match if/else
Viele Fälle auf denselben Wert ✓ Klar strukturiert ✗ Lange Kette
Exhaustivitäts-Garantie ✓ Compiler erzwingt ✗ Manuell
Enum-Abdeckung ✓ Vollständigkeitsprüfung ✗ Keine
OR-Muster case 1 | 2 | 3 ⇒ ○ Umständlich
Bereichsprüfungen case 100..199 ⇒ ○ Möglich, aber unübersichtlich
Komplexe boolesche Logik ✗ Ungeeignet ✓ Natürlicher
Struct-Felder prüfen ✓ Deconstruction ○ Einzelne if-Abfragen
Zwei-Wege-Entscheidung ○ Overkill ✓ Natürlicher

Faustregel: Ab drei Fällen auf denselben Ausdruck ist match klarer. Bei Enums immer match.


11. Pattern Matching in Safety-Code

In DO-178C-zertifiziertem Code ist match besonders wertvoll, weil es Exhaustivität erzwingt. Fehlende Zustände können in sicherheitskritischen Systemen fatale Folgen haben.

enum SystemMode {
    Initializing,
    Standby,
    Active,
    Degraded,       // Eingeschränkter Betrieb wegen Sensorausfall
    Emergency,
    Shutdown
}

@dal(B)
@flight_crit
@stack_limit(256)
fn ApplySystemMode(mode: SystemMode): void {
    match (mode) {
        case SystemMode::Initializing => {
            DisableAllOutputs();
            RunSelfTest();
        }
        case SystemMode::Standby => {
            SetIdleConsumption();
            EnableWakeupSensor();
        }
        case SystemMode::Active => {
            EnableFullOperation();
            StartControlLoop();
        }
        case SystemMode::Degraded => {
            DisableNonCriticalSystems();
            ActivateRedundantSensors();
            AlertCrew();
        }
        case SystemMode::Emergency => {
            ActivateSafeMode();
            TriggerEmergencyProtocol();
            AlertCrew();
        }
        case SystemMode::Shutdown => {
            SaveFlightData();
            DisableAllOutputs();
        }
        // Kein default — jeder neue Modus erzeugt sofort einen Compiler-Fehler
        // Das erzwingt, dass neue Zustände immer bewusst behandelt werden
    }
}

Exhaustivität als Sicherheitsnetz

Wird das System um einen neuen Modus erweitert — z.B. Maintenance — und ApplySystemMode nicht aktualisiert, bricht der Build sofort ab:

error: non-exhaustive match — SystemMode::Maintenance not covered
  --> flight_ctrl.lyx:88:5
  in function: ApplySystemMode
  hint: add 'case SystemMode::Maintenance =>' or 'default =>'

Dieser Fehler tritt beim Build auf — nicht zur Laufzeit in einem Flugzeug.


12. Vollständiges Beispiel: Protokoll-Parser

Ein binärer Protokoll-Parser, der match auf mehreren Ebenen nutzt:

unit FrameParser;

enum FrameType {
    Data        = 0x01,
    Command     = 0x02,
    Ack         = 0x03,
    Nack        = 0x04,
    Heartbeat   = 0xFF
}

enum CommandId {
    Reset       = 0x01,
    Configure   = 0x02,
    Calibrate   = 0x03,
    Shutdown    = 0x0F
}

type Frame = struct {
    frame_type: int64;
    command_id: int64;
    length:     int64;
    payload:    int64;
    checksum:   int64;
};

fn ParseFrame(raw: int64, len: int64): Frame {
    // ... Frame aus Bytes aufbauen ...
    return Frame { frame_type: 0, command_id: 0, length: 0, payload: 0, checksum: 0 };
}

fn ValidateChecksum(frame: Frame): bool {
    return true; // vereinfacht
}

pub fn ProcessFrame(raw: int64, len: int64): void {
    if (len < 5) {
        PrintStr("Frame zu kurz\n");
        return;
    }

    var frame: Frame := ParseFrame(raw, len);

    if (!ValidateChecksum(frame)) {
        PrintStr("Checksummen-Fehler\n");
        return;
    }

    match (frame.frame_type) {
        case FrameType::Data => {
            PrintStr("Datenframe: ");
            PrintInt(frame.length);
            PrintStr(" Bytes\n");
            StoreData(frame.payload, frame.length);
        }
        case FrameType::Command => {
            match (frame.command_id) {
                case CommandId::Reset => {
                    PrintStr("Reset-Kommando\n");
                    PerformReset();
                }
                case CommandId::Configure => {
                    PrintStr("Konfiguration\n");
                    ApplyConfig(frame.payload, frame.length);
                }
                case CommandId::Calibrate => {
                    PrintStr("Kalibrierung\n");
                    RunCalibration();
                }
                case CommandId::Shutdown => {
                    PrintStr("Shutdown-Kommando\n");
                    InitiateShutdown();
                }
                // Alle Kommandos abgedeckt — Compiler schlägt Alarm bei Erweiterung
            }
        }
        case FrameType::Ack => {
            PrintStr("ACK empfangen\n");
            ClearRetryBuffer();
        }
        case FrameType::Nack => {
            PrintStr("NACK empfangen — Wiederholung\n");
            RetransmitLastFrame();
        }
        case FrameType::Heartbeat => {
            ResetWatchdog();
        }
        // Alle FrameType-Werte abgedeckt
    }
}


Best Practices

Situation Empfehlung
Enum-Fallunterscheidung Immer match — kein if/else
Kein default bei Enums Exhaustivität als Sicherheitsnetz nutzen
Viele dichte Einzelwerte match für Jump-Table-Optimierung
Bereiche case lo..hi ⇒ statt x >= lo && x ⇐ hi
Mehrere Werte, gleiche Aktion case a | b | c ⇒
Komplexe Nebenbedingungen Guard mit when
Neue Zustände/Kommandos Build-Fehler durch Exhaustivität — Feature, kein Bug
Safety-Code (DAL-A/B) Kein default bei zustandsbehafteten Enums

Fehlerbehandlung — Result, Tuple-Return, panic()
Bedingungen & Logik — if, while, for
Datentypen — Enums, Structs, Skalare
DO-178C — Sicherheitskritische Programmierung

Letzte Aktualisierung: 2026-05-22