What is an idiomatic way to return Ok(None) if one of multiple Options is None, otherwise unwrap all Options?

Viewed 1206

I have a function that returns Result<Option<[type]>> on an Input type:

pub struct Input {
    a: Option<i32>,
    b: Option<i32>,
    c: Option<i32>,
}

For each field in input if the value is None, the function will immediately return Ok(None) and if there's a value, the function will unwrap it and proceed with further logic:

fn process(input: Input) -> std::io::Result<Option<i32>> {
    let a = match input.a {
        Some(a) => a,
        None => return Ok(None),
    };
    // the same for b and c...
    todo!()
}

The same pattern in repeated for all the fields in the input type. Is there a prettier way to express this?

3 Answers

You could use a if let to assign all variables at once and return Ok(None) in the else branch:

fn process(input: Input) -> std::io::Result<Option<i32>> {
    if let (Some(a), Some(b), Some(c)) = (input.a, input.b, input.c) {
        todo!();
    } else {
        return Ok(None)
    }
}

Playground link

Edit: Or, even more concise (thanks to Dietrich Epp for the suggestion!):

fn process(input: Input) -> std::io::Result<Option<i32>> {
    if let Input {
        a: Some(a),
        b: Some(b),
        c: Some(c),
    } = input
    {
        todo!();
    } else {
        return Ok(None);
    }
}

Playground link

On nightly, you can use try_blocks:

#![feature(try_blocks)]

let x: Option<_> = try { (input.a?, input.b?, input.c?) };

let (a, b, c) = match x {
    Some((a, b, c)) => (a, b, c),
    None => return Ok(None),
};

A stable alternative is creating and immediately calling a closure, although it doesn't look as nice:

let x = (|| { Some((input.a?, input.b?, input.c?)) })();

let (a, b, c) = match x {
    Some((a, b, c)) => (a, b, c),
    None => return Ok(None),
};

Maybe the cleanest solution is Option::and_then combined with the ? operator:

let x = input.a.and_then(|a| Some((a, input.b?, input.c?)));

let (a, b, c) = match x {
    Some((a, b, c)) => (a, b, c),
    None => return Ok(None),
};

You can also use Option::zip, although you'll end up with nested tuples:

let x = input.a.zip(input.b).zip(input.c);

let (a, b, c) = match x {
    Some(((a, b), c)) => (a, b, c),
    None => return Ok(None),
};

Another possibility (but @sk_pleasant's version is way cleaner). Playground

fn main() {
    let inp = Input { a:Some(1), b:Some(2), c:Some(3) };
    let unwrapped = 
        inp.a.map_or(None, |a| 
            inp.b.map_or(None, |b| 
                inp.c.map_or(None, |c| Some((a, b, c)))));   
    match unwrapped {
        Some((a, b, c)) => println!("{} {} {}", a, b, c),
        None => println!("None"), // return Ok(None);
    }
}
Related