Optional Chaining

Nullable member access with the `?.` operator.

Nullable types use ?, and optional member access uses ?..

struct Z {
    val: i32,
}

struct Y {
    z: Z?,
}

struct X {
    y: Y?,
}

fn main() {
    let x: X? = X { y: Y { z: Z { val: 42 } } };
    let v: i32? = x?.y?.z?.val;
    assert v != null;

    let missing: X? = null;
    let none: i32? = missing?.y?.z?.val;
    assert none == null;
}

Rules

Use ?. for each nullable hop. If any receiver in the chain is null, the whole chain yields null and later member lookups are skipped.

The result type is nullable even when the final field is not nullable:

struct User {
    name: str,
}

fn main() {
    let user: User? = User { name: "Ada" };
    let name: str? = user?.name;
    assert name != null;
}

The compiler diagnoses mixed chains that try to continue through nullable values with plain .:

let bad = x?.y.z;

Write x?.y?.z when y is nullable too.

Errors Are Not Optional Values

Optional chaining handles nullable success values only. It does not unwrap throws(...) results. Handle the error first with try or catch, then use ?. on the nullable value:

error UserError {
    Missing
}

struct User {
    name: str,
}

fn load_user(id: i32) throws(UserError) -> User? {
    if id == 0 {
        return null;
    }
    return User { name: "Ada" };
}

fn main() {
    let loaded: str? = (try load_user(1))?.name;
    let fallback: str? = (load_user(0) catch { _ => null })?.name;

    assert loaded != null;
    assert fallback == null;
}

For T? throws(E), try yields T?, and catch arms may return either T or null.