Question mark operator replacement when return type is neither Option nor Result

Viewed 384

Rust has a question mark operator than can be used like this:

fn make_foo() -> Option<Foo> { ... }

fn make_bar() -> Option<Bar> {
    let foo = make_foo()?;
    // ... Many lines of code using `foo` to compute data for `Bar::make` ...
    Some(Bar::make(...))
}

But what if I want a function returning Bar rather than Option<Bar> (thus retuning Bar::new instead of None when make_foo() returns None)?

Of course, I could do

fn make_bar() -> Bar {
    match make_foo() {
        None => { Bar::new() }
        Some(foo) => {
            // ... Many lines of code using `foo` to compute data for `Bar::make` ...
            Bar::make(...)
        }
    }
}

but this increases nesting level for the entire function, which I do not like. I would like to have a replacement for the ? operator.

So I came up with

fn make_bar() -> Bar {
    let foo = match make_foo() {
        None => { return Bar::new(); }
        Some(v) => v
    };
    // ... Many lines of code using `foo` to compute data for `Bar::make` ...
    Bar::make(...)
}

Is this idiomatic Rust? Or are there better solutions?

2 Answers

There's multiple ways to do this and I wouldn't necessarily say one is better than the other. However, let me give you one more option that may be useful if you could be using the ? operator multiple times in this function. Having many early return Bar::new() will likely get old. With this approach you can have your cake and eat it too.

Option has many utility methods. You can combine .and_then() with a final .unwrap_or_else() in this situation:

fn make_bar() -> Bar {
    make_foo()
    .and_then(|foo| {
        // Do stuff with foo
        Some(Bar::make(...))
    })
    .unwrap_or_else(Bar::new)
}

This does increase the nesting level, but only once, and has the advantage that you can use ? inside of the mapping function to cause the outer function to return the default Bar::new() value.

(If you implement Default on Bar then you could even use the less-verbose .unwrap_or_default() at the end.)

Is this idiomatic Rust?

Yes, it looks fine. You can remove the braces:

let foo = match make_foo() {
    None => return Bar::new(),
    Some(v) => v
};

The best will be let-else (unstable):

fn make_bar() -> Bar {
    let Some(foo) = match make_foo() else {
        return Bar::new();
    };
    // ... Many lines of code using `foo` to compute data for `Bar::make` ...
    Bar::make(...)
}

For a similar one, see Idiomatic way to handle errors with an early return in Rust.

Related