if in Rust ist eine Expression, kein Statement — jeder Zweig liefert einen Wert, und das ganze Konstrukt kann direkt einer Bindung zugewiesen oder als Funktions-Rückgabe verwendet werden. Das macht den klassischen ternären Operator (cond ? a : b aus C/Java) überflüssig: Rust löst das mit normaler if-Syntax. Dieser Artikel zeigt den expression-orientierten Stil, geht durch die Typ-Regeln (alle Zweige gleicher Typ), behandelt die ASCII-Striktheit von Bool-Bedingungen und stellt typische Praxis-Patterns vor.

Grundform

Rust Klassisches if
fn main() {
    let zahl = 5;

    if zahl > 0 {
        println!("positiv");
    } else if zahl < 0 {
        println!("negativ");
    } else {
        println!("null");
    }
}

So weit wie in jeder anderen Sprache — Statement-Form mit Side-Effects. Interessanter wird's, wenn if als Wert benutzt wird.

if als Expression

Rust if liefert einen Wert
fn main() {
    let zahl = 5;

    let beschreibung = if zahl > 0 {
        "positiv"
    } else if zahl < 0 {
        "negativ"
    } else {
        "null"
    };

    println!("{beschreibung}");
}

Drei Dinge sind hier zentral:

  • Kein Semikolon nach den String-Literalen in den Zweigen — sie sind die tail expression des jeweiligen Blocks und damit der Block-Wert.
  • Alle Zweige müssen den gleichen Typ liefern — sonst Compile-Fehler.
  • else-Branch ist Pflicht, wenn der if einen Wert produzieren soll.

Das ersetzt das fehlende Ternär:

Rust Ternär-Ersatz
// JavaScript/Java: let max = a > b ? a : b;
let a = 10;
let b = 20;
let max = if a > b { a } else { b };

let stunden = 14;
let begruessung = if stunden < 12 { "Guten Morgen" } else { "Guten Tag" };

Typ-Konsistenz aller Zweige

Rust Typ-Konflikt
fn main() {
    let n = 5;
    // let x = if n > 0 { "positiv" } else { 0 };
    // Fehler E0308: expected `&str`, found integer
}

Der Compiler verlangt, dass beide Branches denselben Typ haben — sonst wäre der Typ von x ambivalent.

Ausnahme: einer der Zweige liefert ! (Never-Type). Dann darf der andere einen beliebigen Typ liefern — ! coerciert sich passend:

Rust Never-Coercion
fn parse_zahl(s: &str) -> i32 {
    let n = if let Ok(v) = s.parse::<i32>() {
        v
    } else {
        panic!("'{s}' ist keine Zahl")     // ! coerciert zu i32
    };
    n
}

Mehr zum Never-Type im Unit-und-Never-Artikel.

Bedingung muss bool sein

Anders als in JavaScript, Python oder C hat Rust kein Truthy/Falsy:

Rust Bedingung strikt bool
let n = 5;
// if n { ... }                  // Fehler — kein bool
// if "text" { ... }              // Fehler
// if Some(1) { ... }             // Fehler

if n != 0 { println!("nicht null"); }
if !s.is_empty() { println!("hat Inhalt"); }
if option.is_some() { println!("vorhanden"); }
# let s = "";
# let option: Option<i32> = None;

Die Bedingung muss explizit bool sein. Wenn ein Zahlen-Vergleich gemeint ist, schreibst du n != 0. Das ist verbal aufwendiger, eliminiert aber eine ganze Klasse von subtilen Bugs (if user.id in JavaScript ist wahr für jede ID außer 0 — gilt das auch als „logged in"? Rust zwingt zur Klarheit).

else if als Kette

Rust else-if-Kette
fn klassifiziere_alter(jahre: u8) -> &'static str {
    if jahre < 18 {
        "Minderjährig"
    } else if jahre < 65 {
        "Erwachsen"
    } else {
        "Senior"
    }
}

Das ist syntaktischer Zucker für ein verschachteltes if-else. Bei vielen Ästen wird match lesbarer — siehe Pattern-Matching-Kapitel.

if let — kompakter Pattern-Match

Wenn die Bedingung darin besteht, ein Pattern zu matchen (häufig Option/Result), gibt es eine spezielle Form: if let. Sie bekommt einen eigenen Artikel; hier nur ein Vorschau-Beispiel:

Rust if let
let maybe: Option<i32> = Some(42);
if let Some(n) = maybe {
    println!("Wert: {n}");
}

Praxis: if-Expressions in echtem Code

Default-Wert mit Override

Rust Default oder Override
fn timeout_ms(custom: Option<u64>) -> u64 {
    if let Some(t) = custom { t } else { 5_000 }    // 5s default
}
// Oder noch idiomatischer:
fn timeout_ms_v2(custom: Option<u64>) -> u64 {
    custom.unwrap_or(5_000)
}

Variante 2 ist idiomatischer, wenn Option::unwrap_or reicht — aber if let zeigt mehr Logik, falls noch andere Checks dazukommen.

HTTP-Statuscode klassifizieren

Rust HTTP-Klassifikation
fn kategorie(status: u16) -> &'static str {
    if status < 200      { "1xx Informational" }
    else if status < 300 { "2xx Success" }
    else if status < 400 { "3xx Redirect" }
    else if status < 500 { "4xx Client Error" }
    else                  { "5xx Server Error" }
}

fn main() {
    assert_eq!(kategorie(200), "2xx Success");
    assert_eq!(kategorie(404), "4xx Client Error");
}

Klassische else-if-Kette für überschneidungsfreie Bereiche. Lesbar, ohne match mit Ranges.

Rabatt-Berechnung

Rust Bedingte Berechnung
struct Bestellung { artikel: u32, gesamt_cent: u32, ist_premium: bool }

fn rabatt_cent(b: &Bestellung) -> u32 {
    // Hauptlogik in einer if-Expression
    let prozent: u32 = if b.ist_premium && b.gesamt_cent >= 10_000 {
        15
    } else if b.gesamt_cent >= 5_000 {
        10
    } else if b.artikel >= 5 {
        5
    } else {
        0
    };
    b.gesamt_cent * prozent / 100
}

Statt mehrerer mut-Mutations sammelt eine einzige if-Expression den Rabatt-Prozentsatz. Lesbarer, weil der Wert sofort an die Bindung gebunden ist.

Theme-Auswahl beim Render

Rust Render-Style
struct UserPrefs { dark_mode: bool }

fn render_hintergrund(prefs: &UserPrefs) -> &'static str {
    if prefs.dark_mode { "#1a1a1a" } else { "#ffffff" }
}

fn render_text(prefs: &UserPrefs) -> &'static str {
    if prefs.dark_mode { "#e0e0e0" } else { "#202020" }
}

Einzeilige if-Expressions als Funktion-Body — kompakt, klar, kein return nötig.

Validierungs-Funktion mit frühem Ausstieg

Rust Validierung
struct Form { email: String, alter: u8, akzeptiert: bool }

fn validiere(f: &Form) -> Result<(), &'static str> {
    if f.email.is_empty() { return Err("Email fehlt"); }
    if !f.email.contains('@') { return Err("Email ungültig"); }
    if f.alter < 18 { return Err("zu jung"); }
    if !f.akzeptiert { return Err("AGB nicht akzeptiert"); }
    Ok(())
}

Mehrere unabhängige if-Checks mit Early-Return — sehr lesbarer Validator. Alternative: alle Conditions mit || verkettet — aber dann verliert man die spezifische Fehlermeldung.

Logging-Level entscheiden

Rust Log-Level
struct Result_ { status: u16, latency_ms: u64 }

fn log_level(r: &Result_) -> &'static str {
    if r.status >= 500 { "ERROR" }
    else if r.status >= 400 || r.latency_ms > 5000 { "WARN" }
    else if r.latency_ms > 1000 { "INFO" }
    else { "DEBUG" }
}

Bedingungen kombiniert mit || — die Logik liest sich wie eine Anforderungsbeschreibung.

Interessantes

Klammern um die Bedingung sind nicht nötig.

if (n > 0) ist gültig, aber unidiomatisch. Idiomatisch: if n > 0. Klammern sind nur Code-Smell, wenn sie die Lesbarkeit nicht messbar verbessern.

if ohne else als Expression ist verboten.

let x = if cond { 5 }; wäre für den false-Fall undefiniert — der Compiler verlangt einen else-Branch, sobald if als Wert genutzt wird. Statement-Form (if cond { do(); }) ohne else ist erlaubt.

Alle Branches müssen gleichen Typ liefern — oder !.

Eine häufige Stolperfalle: let x = if cond { 5 } else { "fünf" }; — Type-Mismatch. Ausnahme: panic!, return, loop {} haben Typ ! und coercen sich in jeden anderen Typ. So funktioniert let x = if cond { 5 } else { panic!() };.

Performance ist identisch zur Statement-Form.

Der Compiler optimiert if-Expressions zu denselben Maschinen-Instruktionen wie ein klassisches if/else mit Zuweisungen in jedem Branch. Es gibt keinen Laufzeit-Overhead für expression-orientierten Stil.

Bei mehr als 3 else-if besser match.

Eine else if-Kette mit 5+ Branches wird unübersichtlich. match mit Range-Patterns (0..=99 => ...) oder Guard-Patterns ist meist die bessere Wahl — siehe Pattern-Matching-Kapitel.

Clippy warnt vor if cond { true } else { false }.

Solche tautologischen Konstrukte sind direkt der Bedingung selbst. Clippy schlägt vor: cond statt der ganzen if-Expression. Gleichermaßen if !cond { false } else { true }cond.

if-Expressions können in Closure-Bodies und Match-Armen leben.

Mit der Expression-Semantik passen if-Bedingungen überall hin, wo ein Wert erwartet wird — auch tief in Closure-Chains: iter.map(|x| if x > 0 { x } else { 0 }). Sehr idiomatisch für bedingtes Mapping.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Kontrollfluss

Zur Übersicht