Option<T> ist Rusts Antwort auf das Null-Problem. In Sprachen wie Java, C, JavaScript ist jede Referenz potenziell null — und vergessenes Null-Check ist eine der häufigsten Bug-Quellen überhaupt (Tony Hoare nannte es seinen „Billion-Dollar-Mistake"). Rust eliminiert das Problem komplett: ein Wert ist entweder Some(T) (vorhanden) oder None (nicht vorhanden). Der Compiler verlangt, dass du beide Fälle behandelst, bevor du auf den Wert zugreifst. Dieser Artikel zeigt das vollständige API von Option, die wichtigsten Patterns und macht klar, warum diese eine Typ-Idee viele klassische Bugs eliminiert.

Definition

Option<T> ist ein einfaches Stdlib-Enum:

Rust Stdlib-Definition
// Aus der Stdlib (vereinfacht):
pub enum Option<T> {
    None,
    Some(T),
}

Beide Varianten sind so wichtig, dass sie im Prelude sind — du kannst Some(...) und None ohne Option::-Präfix nutzen.

Rust Verwendung
fn main() {
    let a: Option<i32> = Some(42);
    let b: Option<i32> = None;
    println!("{a:?} {b:?}");        // Some(42) None
}

Niche-Optimization

Eine besondere Eigenheit: für viele Typen ist Option<T> genauso groß wie T selbst:

Rust Niche-Optimization
use std::mem::size_of;

fn main() {
    println!("{}", size_of::<&i32>());                    // 8
    println!("{}", size_of::<Option<&i32>>());            // 8 — gleich!
    println!("{}", size_of::<Box<i32>>());                 // 8
    println!("{}", size_of::<Option<Box<i32>>>());         // 8 — gleich!
}

&T und Box<T> haben den Null-Pointer nicht als legalen Wert. Der Compiler nutzt das 0x0...0-Pattern als None-Marker. Damit ist Option<&T> syntaktisch typsicher und im Speicher identisch zu einem nullable Pointer in C.

Pattern-Matching auf Option

Rust match
fn beschreibe(o: Option<i32>) -> String {
    match o {
        Some(n) => format!("Wert: {n}"),
        None => "kein Wert".to_string(),
    }
}

fn main() {
    println!("{}", beschreibe(Some(42)));     // "Wert: 42"
    println!("{}", beschreibe(None));         // "kein Wert"
}

Klassisches match mit beiden Varianten. Der Compiler verlangt beide Branches.

if let — kompakter

Rust if let
fn drucke_wenn_da(o: Option<i32>) {
    if let Some(n) = o {
        println!("{n}");
    }
    // None-Fall wird ignoriert
}

Wenn nur der Some-Fall interessiert: if let. Mehr im if-let-Artikel.

Das wichtigste API

unwrap — Wert oder Panic

Rust unwrap
fn main() {
    let o = Some(42);
    let n = o.unwrap();         // 42
    // None.unwrap();           // Panic — „called Option::unwrap on a None value"
}

unwrap() extrahiert den Wert oder panickt. Nur in Tests, Prototypen, oder wenn du beweisbar sicher bist, dass None unmöglich ist.

expect — Wert oder Panic mit Message

Rust expect
fn main() {
    let o: Option<i32> = std::env::var("PORT").ok().and_then(|s| s.parse().ok());
    let port = o.expect("PORT muss gesetzt sein und u32 sein");
    println!("{port}");
}

Wie unwrap, aber mit klarer Fehler-Meldung. Idiomatisch für „Hier ist None unmöglich, weil...".

unwrap_or — Default

Rust unwrap_or
fn main() {
    let o: Option<i32> = None;
    let n = o.unwrap_or(0);     // 0
    assert_eq!(n, 0);

    let o2 = Some(42);
    let m = o2.unwrap_or(0);    // 42
    assert_eq!(m, 42);
}

unwrap_or(default) — der Default-Wert wird sofort ausgewertet, auch wenn Some vorliegt.

unwrap_or_else — lazy Default

Rust unwrap_or_else
fn main() {
    let o: Option<String> = None;
    let s = o.unwrap_or_else(|| {
        // teure Berechnung — nur ausgeführt bei None
        std::env::var("DEFAULT").unwrap_or_else(|_| "unbekannt".into())
    });
    println!("{s}");
}

Mit Closure — wird nur ausgewertet bei None. Wichtig, wenn der Default teuer zu konstruieren ist.

unwrap_or_default

Rust unwrap_or_default
fn main() {
    let o: Option<String> = None;
    let s = o.unwrap_or_default();      // ""
    assert_eq!(s, "");

    let o2: Option<i32> = None;
    let n = o2.unwrap_or_default();     // 0
    assert_eq!(n, 0);
}

Default via Default-Trait. Funktioniert für alle Typen, die Default implementieren.

Transformationen

map — auf den Wert anwenden

Rust map
fn main() {
    let o = Some(5);
    let m = o.map(|n| n * 2);        // Some(10)
    assert_eq!(m, Some(10));

    let leer: Option<i32> = None;
    let leer2 = leer.map(|n| n * 2); // None — Closure läuft nicht
    assert_eq!(leer2, None);
}

map transformiert den inneren Wert, falls vorhanden. Klassisches Funktional-Pattern.

and_then (flat_map)

Rust and_then
fn parse_zahl(s: &str) -> Option<i32> {
    s.parse().ok()
}

fn verdopple(n: i32) -> Option<i32> {
    if n < 0 { None } else { Some(n * 2) }
}

fn main() {
    let r = Some("5").and_then(|s| parse_zahl(s)).and_then(verdopple);
    assert_eq!(r, Some(10));

    let r2 = Some("abc").and_then(|s| parse_zahl(s)).and_then(verdopple);
    assert_eq!(r2, None);
}

and_then chaint Option-Operationen. Die nächste Closure läuft nur, wenn der vorherige Some ergab.

filter — bedingt auf None setzen

Rust filter
fn main() {
    let o = Some(42);
    let gerade = o.filter(|&n| n % 2 == 0);     // Some(42)
    let ungerade = Some(43).filter(|&n| n % 2 == 0);    // None
    assert_eq!(gerade, Some(42));
    assert_eq!(ungerade, None);
}

filter macht aus Some(v) ein None, wenn das Prädikat false liefert.

ok_or — zu Result konvertieren

Rust ok_or
fn main() {
    let o: Option<i32> = Some(42);
    let r: Result<i32, &str> = o.ok_or("kein Wert");
    assert_eq!(r, Ok(42));

    let n: Option<i32> = None;
    let r2: Result<i32, &str> = n.ok_or("kein Wert");
    assert_eq!(r2, Err("kein Wert"));
}

ok_or(err) baut aus Option<T> ein Result<T, E> — sehr nützlich am Übergang von Optional zu Fallible.

Der ?-Operator auf Option

Der ?-Operator funktioniert in Funktionen, die Option zurückgeben — bei None macht er ein early-return:

Rust ? auf Option
fn ersten_buchstaben(s: &str) -> Option<char> {
    let erstes_wort = s.split_whitespace().next()?;     // None → return None
    erstes_wort.chars().next()                           // erstes Zeichen
}

fn main() {
    assert_eq!(ersten_buchstaben("Hallo Welt"), Some('H'));
    assert_eq!(ersten_buchstaben(""), None);
}

? macht aus Option<T> ein T (wenn Some) oder propagiert None als Funktions-Rückgabe. Sehr kompakt für Chains, in denen jeder Schritt fehlschlagen kann.

Weitere wichtige Methoden

MethodeWas sie tut
is_some()true wenn Some
is_none()true wenn None
as_ref()Option<T>Option<&T>
as_mut()Option<T>Option<&mut T>
take()Wert herausnehmen, None zurücklassen
replace(x)Wert ersetzen, alten zurückgeben
or(other)self wenn Some, sonst other
or_else(f)self wenn Some, sonst f()
xor(other)genau eines von beiden, oder None
zip(other)Option<(A, B)> aus zwei Options
flatten()Option<Option<T>>Option<T>
contains(&x)true wenn Some(x) mit x == self.0
Rust as_ref + map
fn main() {
    let s = Some(String::from("Hi"));
    // Mit as_ref: nicht konsumieren
    let laenge: Option<usize> = s.as_ref().map(|x| x.len());
    // s ist hier noch nutzbar
    println!("{s:?} hat Länge {laenge:?}");
}

as_ref() ist sehr nützlich, wenn du den Option-Wert nicht verbrauchen willst.

take — Wert herausziehen

Rust take
fn main() {
    let mut o = Some(String::from("Hallo"));
    let extrahiert = o.take();          // o ist jetzt None
    assert_eq!(extrahiert, Some(String::from("Hallo")));
    assert_eq!(o, None);
}

take() nimmt den Wert aus dem Option heraus und ersetzt durch None. Sehr nützlich, wenn du in einer &mut self-Methode den Wert haben willst, ohne self zu verbrauchen.

Praxis: Option im echten Code

Optionale Config-Werte

Rust Konfiguration
pub struct AppConfig {
    pub host: String,
    pub port: u16,
    pub log_datei: Option<String>,           // optional
    pub max_workers: Option<u32>,            // optional
}

impl AppConfig {
    pub fn workers(&self) -> u32 {
        self.max_workers.unwrap_or(4)        // Default 4
    }

    pub fn loggt_in_datei(&self) -> bool {
        self.log_datei.is_some()
    }
}

Option<T> für optionale Felder. Default mit unwrap_or, Vorhandensein mit is_some.

Cache-Lookup

Rust Cache
use std::collections::HashMap;

pub struct Cache { eintraege: HashMap<String, Vec<u8>> }

impl Cache {
    pub fn holen(&self, key: &str) -> Option<&[u8]> {
        self.eintraege.get(key).map(|v| v.as_slice())
    }
}

fn main() {
    let mut c = Cache { eintraege: HashMap::new() };
    c.eintraege.insert("user:42".into(), vec![1, 2, 3]);

    // Idiomatischer Zugriff mit if let
    if let Some(daten) = c.holen("user:42") {
        println!("Cache-Hit: {} Bytes", daten.len());
    } else {
        println!("Cache-Miss");
    }
}

HashMap::get gibt Option<&V>. Mit .map(...) weiter verarbeitet.

Funktion mit „kann fehlen"-Semantik

Rust Suchen
pub fn finde_user_by_email(email: &str, users: &[(u64, String)]) -> Option<u64> {
    users.iter()
        .find(|(_, mail)| mail == email)
        .map(|(id, _)| *id)
}

fn main() {
    let users = vec![
        (1, "anna@example.com".to_string()),
        (2, "bert@example.com".to_string()),
    ];
    assert_eq!(finde_user_by_email("anna@example.com", &users), Some(1));
    assert_eq!(finde_user_by_email("unbekannt@example.com", &users), None);
}

find plus map — sehr typisches Such-Pattern.

Chain von Operationen, jede kann fehlen

Rust Chain
pub fn ersten_satz(text: &str) -> Option<&str> {
    let trimmed = text.trim();
    if trimmed.is_empty() {
        return None;
    }
    trimmed.find('.').map(|i| &trimmed[..i])
}

fn main() {
    assert_eq!(ersten_satz("Hallo. Welt."), Some("Hallo"));
    assert_eq!(ersten_satz(""), None);
    assert_eq!(ersten_satz("Keine Punkt"), None);
}

Zwei Möglichkeiten zum frühen Ausstieg: is_emptyreturn None, dann find('.') mit map.

Iterator mit filter_map

Rust filter_map
fn main() {
    let raw_zahlen = ["42", "abc", "100", "x", "7"];
    let valide: Vec<i32> = raw_zahlen.iter()
        .filter_map(|s| s.parse().ok())
        .collect();
    assert_eq!(valide, vec![42, 100, 7]);
}

parse() gibt Result, .ok() macht es zu Option. filter_map collect't nur die Some-Werte — eine sehr elegante Filter-und-Transform-Operation.

Field-Update mit take

Rust State-Mutation
pub struct Buffer { inhalt: Option<Vec<u8>> }

impl Buffer {
    pub fn entnehmen(&mut self) -> Vec<u8> {
        self.inhalt.take().unwrap_or_default()
    }

    pub fn schreiben(&mut self, daten: Vec<u8>) {
        self.inhalt = Some(daten);
    }
}

fn main() {
    let mut b = Buffer { inhalt: Some(vec![1, 2, 3]) };
    let d = b.entnehmen();
    assert_eq!(d, vec![1, 2, 3]);
    assert!(b.inhalt.is_none());
}

take() extrahiert den Wert aus dem Option und setzt es auf None — ohne self zu verbrauchen.

Optionale Funktion-Parameter

Rust API-Design
pub fn baue_url(host: &str, port: Option<u16>) -> String {
    match port {
        Some(p) => format!("https://{host}:{p}"),
        None => format!("https://{host}"),
    }
}

fn main() {
    assert_eq!(baue_url("example.com", None), "https://example.com");
    assert_eq!(baue_url("example.com", Some(8443)), "https://example.com:8443");
}

Explizite Optionalität in der Signatur — kein „magisches null".

Frühe Validierung mit ? und ok_or

Rust Validierung
pub fn extrahiere_domain(email: &str) -> Result<&str, &'static str> {
    let at_position = email.find('@').ok_or("kein @")?;
    let domain = &email[at_position + 1..];
    if domain.is_empty() {
        return Err("Domain leer");
    }
    Ok(domain)
}

find gibt Option, ok_or macht es zu Result, ? propagiert beim Err. Sehr kompakte Validierungs-Pipeline.

Default + Override

Rust Override-Pattern
pub fn timeout_ms(custom: Option<u64>) -> u64 {
    custom.unwrap_or(5_000)
}

pub fn timeout_ms_v2(custom: Option<u64>) -> u64 {
    custom.unwrap_or_else(|| {
        // teure Default-Berechnung (z. B. aus Config)
        aus_config()
    })
}

fn aus_config() -> u64 { 5_000 }

unwrap_or mit konstantem Default, unwrap_or_else mit lazy berechnetem Default.

Optional verschachtelt — flatten

Rust Nested Option
pub fn finde_user_email(users: &[(u64, Option<String>)], id: u64) -> Option<&String> {
    users.iter()
        .find(|(uid, _)| *uid == id)        // Option<&(u64, Option<String>)>
        .map(|(_, mail)| mail.as_ref())     // Option<Option<&String>>
        .flatten()                          // Option<&String>
}

fn main() {
    let users = vec![
        (1, Some("anna@example.com".to_string())),
        (2, None),
    ];
    assert!(finde_user_email(&users, 1).is_some());
    assert!(finde_user_email(&users, 2).is_none());
    assert!(finde_user_email(&users, 99).is_none());
}

flatten() macht aus Option<Option<T>> ein Option<T> — sehr nützlich bei verschachtelten Optionen.

FAQ

Wann unwrap?

Nur in Tests, Beispielen, oder wenn du beweisen kannst, dass None unmöglich ist. Im normalen Code: ?, unwrap_or, if let, match. expect("warum None hier unmöglich ist") ist besser als unwrap, weil der Panic mit Kontext kommt.

unwrap_or oder unwrap_or_else?

unwrap_or(default) wenn default billig ist (eine Konstante, ein Zero-Wert). unwrap_or_else(|| ...) wenn default eine teure Berechnung oder Allocation ist — dann wird sie nur bei None ausgewertet.

?-Operator auf Option vs. Result.

? funktioniert auf beiden — abhängig vom Rückgabe-Typ der umliegenden Funktion. Bei fn foo() -> Option<T> propagiert ? ein None. Bei fn foo() -> Result<T, E> propagiert ? ein Err. Mischformen brauchen ok_or zur Konvertierung.

Niche-Optimization macht Option<&T> kostenlos.

Option<&T> ist 8 Bytes auf 64-bit, identisch zu &T. Der Null-Pointer dient als None. Damit ist Option<&T> so effizient wie ein nullable Pointer in C — aber typsicher prüfbar.

map vs. and_then.

.map(f) wenn f ein normaler Wert zurückgibt: Option<T> → Option<U>. .and_then(f) wenn f selbst ein Option<U> zurückgibt — sonst hättest du Option<Option<U>>. Auch bekannt als „flatMap" in anderen Sprachen.

filter macht aus Some(v) None, wenn Predikat false.

Some(42).filter(|&n| n > 100) ergibt None. Nützlich für „nimm den Wert nur, wenn ...". Kombiniert mit map und and_then zu Functional-Style-Pipelines.

take ist für &mut self-Methoden Gold.

Wenn du in einer Methode mit &mut self-Receiver einen Option<Vec<...>>-Wert herausnehmen willst (ohne self zu verbrauchen), ist take() die Lösung. Standard-Pattern für „Field auf None setzen und alten Wert verarbeiten".

Vermeide if x.is_some() { x.unwrap() }.

Anti-Pattern. Stattdessen: if let Some(v) = x { ... }. Genau ein Pattern-Match statt zwei separate Checks. Clippy warnt darauf.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Enums & Pattern Matching

Zur Übersicht