Kiste
EN

Null-Safe-Operatoren `?.`, `??`, `??=`

Verfügbar — implementiert in Phase B.5

In jedem Programm gibt es Werte, die manchmal da sind und manchmal nicht da — z.B. das Ergebnis eines Karten-Lookups oder einer optionalen Konfiguration. In Kiste signalisiert „nicht da" der Wert nichts.

Ohne Hilfsmittel wird der Umgang mit nichts-haltigen Werten schnell unleserlich:

nimm strasse = nichts
wenn person != nichts {
    wenn person.adresse != nichts {
        strasse = person.adresse.strasse
    }
}

Mit den drei Null-Safe-Operatoren wird daraus eine einzige, klare Zeile:

nimm strasse = person?.adresse?.strasse ?? "Unbekannt"

Diese beiden Schnipsel sind schematisch (stell dir ein person-Objekt mit verschachtelter Adresse vor) — die lauffähigen Beispiele folgen ab §15.1.

15.1 ?. — Sicherer Member-Zugriff

a?.b liefert a.b, wenn a nicht nichts ist — sonst nichts, ohne Fehler:

nimm person = {"name": "Sascha"}
sag person?.name           // Sascha

nimm leer = nichts
sag leer?.name             // nichts (kein Crash)

Short-Circuit über die ganze Kette: sobald in einer Punkt-Kette ein ?. nichts ergibt, wird alles dahinter übersprungen — auch normale ., Methoden-Aufrufe und Index-Zugriffe:

nimm person = nichts
sag person?.adresse.strasse[0].länge   // nichts (alles ab person?. wird übersprungen)

Methoden auch sicher:

nimm wert = lese_input()        // kann nichts sein
sag wert?.als_text() ?? "leer"
// Wenn wert nichts ist, wird .als_text() GAR NICHT aufgerufen.
// Argumente werden auch nicht evaluiert.

?. reagiert NUR auf nichts: auf einen falschen Typ (z.B. 5?.feld) wirft ?. weiterhin einen Laufzeit-Fehler — ?. ist ein Null-Safe-Operator, kein „alles-was-schiefgeht-fängt-er-ab".

15.2 ?? — Nichts-Coalescing (Default-Operator)

a ?? b liefert a, wenn a nicht nichts ist — sonst b:

nimm port = config.port ?? 8080
nimm name = person.name ?? "Anonym"

Verkettung liefert das erste nicht-nichts:

nimm anzeige = person.name ?? person.spitzname ?? "anonym"

Short-Circuit: der rechte Operand wird nur evaluiert, wenn der linke nichts ist. Das ist nicht nur Optimierung, sondern Korrektheit, wenn rechts Seiteneffekte stehen:

nimm wert = cache[schlüssel] ?? teure_berechnung()
// teure_berechnung() läuft NUR bei Cache-Miss.

Wichtige Edge-Case-Tabelle

?? löst NUR bei nichts aus — NICHT bei „falsy" Werten wie 0 oder "":

Linker Wert a ?? "fallback" ergibt
nichts "fallback" (rechter Wert)
0 0 (NICHT "fallback"!)
"" (leerer Text) "" (NICHT "fallback"!)
falsch falsch (NICHT "fallback"!)
[] (leere Liste) [] (NICHT "fallback"!)
{} (leere Karte) {} (NICHT "fallback"!)
Alle anderen Werte linker Wert

Warum so streng? Weil JavaScript jahrelang || für Defaults verwendet hat — und damit Bugs bei jedem 0, "" oder false produziert hat. Kiste vermeidet das von Anfang an: ?? heißt buchstäblich „falls nichts".

In Kiste gibt es keinen Operator, der „falsy" als Default behandelt. Wer alle leeren Werte ersetzen will, schreibt das bewusst:

nimm name = person.name
wenn person.name == "" oder person.name == nichts {
    name = "Unbekannt"
}

Mehr Code, aber bewusster Code. Die Stelle, wo „leer" als „fehlend" behandelt wird, ist sichtbar — nicht versteckt im Operator.

15.3 ??= — Coalescing-Zuweisung

a ??= b ist Kurzform für „wenn a nichts ist, setze a = b". Das klassische Idiom für Lazy-Initialization und Caching:

// Statt:
wenn cache[schlüssel] == nichts {
    cache[schlüssel] = berechne()
}

// Direkter:
cache[schlüssel] ??= berechne()

berechne() wird nicht ausgeführt, wenn der Cache-Slot bereits gesetzt ist — Short-Circuit auch hier.

15.4 Mischung von ?? mit oder / und

Beide Operatoren können naheliegende Mehrdeutigkeiten erzeugen:

nimm x = a ?? b oder c
// Soll das (a ?? b) oder c sein, oder a ?? (b oder c)?

Beide Lesarten sind plausibel — beide können Bugs verursachen. Kiste lässt das nicht zu: Mischung ohne Klammern ist ein Parser-Fehler:

nimm x = a ?? b oder c          // Fehler: bitte klammern
nimm x = (a ?? b) oder c        // ok
nimm x = a ?? (b oder c)        // ok
nimm x = a ?? b ?? c            // ok (nur ??)
nimm x = a oder b oder c        // ok (nur oder)

Das ist exakt das Verhalten von modernem JavaScript (ES2020+). „Explizit über implizit" — der Compiler zwingt zur Entscheidung statt eine willkürliche Default-Reihenfolge zu wählen.

15.5 Häufiges Idiom: ?. und ?? zusammen

Der mit Abstand häufigste Use-Case:

// "Lies das Feld — falls die Struktur nicht existiert oder das Feld leer
//  ist, nimm den Default."
nimm anzeige = person?.name ?? "Anonym"
nimm port    = config?.server?.port ?? 8080
nimm titel   = artikel?.metadaten?.titel ?? "Ohne Titel"

Mehr Details und Edge-Cases in docs/null-safe.md.