None propagation to a value

Viewed 79

Is there a better way to propagate None into a value (not just return None), other that wrapping it into a function like this to allow use of ? operator (value itself is Option)?

struct A {
    b: Option<B>
}

struct B {
    c: Option<C>
}

struct C {
    val: i32,
    val_optional: Option<i32>
}

fn propagate<T>(f: impl FnOnce() -> Option<T>) -> Option<T> {
    return f();
}

#[test]
fn foo() {
    let abcv = Some(A {b: Some(B {c: Some(C {val: 42, val_optional: Some(42)})})});
    let val = propagate(|| abcv?.b?.c?.val_optional);//why not allow just let val = abcv?.b?.c?.val_optional ?
    assert_eq!(val, Some(42));

    let ab = Some(A { b: Some(B { c: None }) });
    let val = propagate(|| ab?.b?.c?.val_optional);//why not allow just let val = abcv?.b?.c?.val_optional ?
    assert_eq!(val, None);



    let abcv = Some(A {b: Some(B {c: Some(C {val: 42, val_optional: Some(42)})})});
    let val = propagate(|| Some(abcv?.b?.c?.val));//why not allow just let val = Some(abcv?.b?.c?.val) ?
    assert_eq!(val, Some(42));

    let ab = Some(A { b: Some(B { c: None }) });
    let val = propagate(|| Some(ab?.b?.c?.val));//why not allow just let val = Some(abcv?.b?.c?.val) ?
    assert_eq!(val, None);
}
3 Answers

You could do something like this:

let abcv = Some(A {
    b: Some(B {
        c: Some(C {
            val: 42,
            val_optional: Some(42),
        }),
    }),
});

// returns the option 'val_optional'
let val_optional: Option<i32> = abcv
    .and_then(|a| a.b)
    .and_then(|b| b.c)
    .and_then(|c| c.val_optional);

// you can then get the value or if its None, take 0
let res: i32 = val_optional.unwrap_or(0);

The ? operator on an Option<T> does an early return None when applied to a variable whose value is None. So in your second example, if you did (as you propose)

val = ab?.b?.c?.val_optional;

But ab?.b?.c? == None, then foo() would do an early return there (but the Rust compiler doesn't allow that because as you've written it, foo() returns () and not an optional).

So you want to call ab?.b?.c?.val_optional inside a function so that the early return doesn't make foo() return early. Though you don't really need propagate(), as you could just use a closure like this:

let val = (|| ab?.b?.c?.val_optional)();

This is something the Rust developers are actively working on: try blocks.

They are not ready yet (there are issues around inference), but they will likely look like:

#![feature(try_blocks)]

let val = try { Some(abcv?.b?.c?.val) };

In the meantime, you can use an immediately invoked closure as @Andrew suggested.

Related