How to exit a function early with Ok if None is received

This is my first post to users.rust-lang. I'm new to the language and I'm trying to figure out if there's an idiomatic way to exit a function early with Ok(()) in the case that None is received. But I want to proceed to immutably assign in the original scope if Some is received.

For example, imagine that we have a function with this signature.


fn maybe_int() -> Option<u32> {
    // sometimes I return None, othertimes I return Some(x)
    Some(1)
}

And we want to use that function from a calling function that returns a Result. I want to assign the value of Some in the calling scope, and exit the calling function with Ok(()) if None was received from maybe_int.

But this cannot be done with unwrap_or_else

fn some_caller() -> Result<(), String> {
    let value = maybe_int().unwrap_or_else(|| {
        // Help, I want some_caller to return Ok(()) now
        // but I'm stuck in a lambda.
        1 // The one is here just so that it compiles, but I
          // actually want some_caller to exit Ok
    });

    // proceed to do things with value, preferably in some_caller's scope instead of
    // an indented block. some things might_error()?;
    Ok(())
}

One way that I know of to write this code is to match on the result, but then I'm left with my value in an inner scope when I wanted it in the top-level scope.

fn one_solution() -> Result<(), String> {
    match maybe_int() {
        None => Ok(()),
        Some(value) => {
            // yuck, proceed to do stuff with value in this deeply
            // indented block. some of it might_error()?;
            Ok(())
        }
    }
}

The only other thing I can think of is to start with a mut and assign to it inside the Some block, but this seems very clunky. I suppose I could even let value = value; after to make it immutable, but yuck.

fn another_solution() -> Result<(), String> {
    // this works but seems very non-idiomatic and leaves me with a mut value. yuck.
    let mut value: u32 = 0;
    if let Some(temp) = maybe_int() {
        value = temp;
    } else {
        return Ok(());
    }
    // do stuff with value. some of it might_error()?;
    Ok(())
}

So the question: is there an idiomatic way to do what I'm trying to do? That is, unwrap the Option into the original scope, but return early from the function with Ok(()) if None is received.

P.S. My aversion to doing everything in the nested Some block of one_solution might just be stylistic baggage that I'm bringing with me.

There isn't an idiomatic way to return Ok early.

This solution doesn't need to be mutable if you just skip the 0-initialization. Rust will see that the value will be set exactly once on the only path that succeeds the if-else.

You can also initialize a value from directly from a match or if-else since they're expressions.

let value = match maybe_int() {
    None => return Ok(()),
    Some(value) => value,
};
4 Likes

I'd say @cuviper's code is the idiomatic solution here. It makes it clear what's being done, and it's a lot less clunky than using an if/else and/or assigning to a mutable variable.

It's still a bit verbose, but I think that's appropriate as you're doing something not often done, and having a match go to the value or a return explicitly states what you're doing pretty nicely.

However, if you do this a ton, and you have reason to believe most developers reading your code will expect this pattern, then this can be simplified with a macro. Something like this would remove all the boilerplate:

macro_rules! early_ok {
    ($e:expr) => {
        match $e {
            None => return Ok(()),
            Some(v) => v,
        }
    };
}

This would be used like the old try! macro we had before ? was introduced:

let value = early_ok!(maybe_int());

Really though, if you get to that point, I would recommend refactoring so that your early returns end up in Result::Err so you can use ?. It could be perfectly valid to have an error enum with a "actually success; skipped cases" variant, like

enum CalculationErr {
    /// Success - skipped some checks
    SkippedChecks,
    Err(String),
}

It's not ideal, but it's another alternative to consider?

If/when Try is stabilized, Result<(), CalculationErr> could be replaced with a single CalculationResult enum, and that would clearer semantics while still using ? to just propagate up the success-but-skipped-checks case.

1 Like
fn best() -> Result<(), String> {
    let value = match maybe_int() {
        None => return Ok(()),
        Some(value) => value,
    };
    Ok(())
}

Weird how my brain assumed that this wouldn't compile. I guess I was assuming the match arms had to both result in the same type. But I guess the early return match arm is not included in that type check because it leaves the function.

Thanks.

Thank you for that alternate solution. I feel like a NoError Error would be weird, but it would be a potential solution.

1 Like

The return is a diverging expression, which has the never type ! that can implicitly cast to any other type -- so it does satisfy the required type of that match arm. There's another example of this here:
https://doc.rust-lang.org/rust-by-example/fn/diverging.html

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.