Validating multiple Options at once

This might be a really dumb question, but I can't figure out a nice way to do it.

I have a function which accepts (let's say) three Option arguments, and I want to perform a validation step where I check that all are Some, then do something with those values if they are.

The naive approach below doesn't work because Option isn't copyable. I could provide a default value for each (maybe the empty string) and unwrap them initially, or do it one by one, but I don't know what the idiomatic way to do this for several at the same time is. Is this even a good idea in Rust?

fn f(a: Option<String>, b: Option<String>, c: Option<String>) -> Option<String> {
    if a.or(b).or(c).is_none() {
        println!("a, b, and c are all required");
        return None;
    }

    let a1 = a.unwrap();
    let b1 = b.unwrap();
    let c1 = c.unwrap();

    Some(format!("{}:{}:{}", a1, b1, c1))
}

A nice way to do it is to put them all in a 3-element tuple, then pattern-match them back out again:

if let (Some(a), Some(b), Some(c)) = (a, b, c) {
    println!("You passed in {:?}, {:?}, and {:?}");
}
else {
    println!("Need to pass in all three arguments");
}
4 Likes

Thanks, that looks nice. I tried adding that, but I can't get it to work (still complains about using a moved value when I try to unwrap). If I provide different names on the LHS, I don't seem to get the variables assigned at all.

Is there somewhere I can read up on how that pattern-matching in an assignment should work?

Is there a reason why you can't do:

if a.is_some()
   && b.is_some()
   && c.is_some() {
    …
}
else { … }

Regarding @ogham 's solution, in case you need a, b and c after the if let block, you can it adapt to:

if let (&Some(a), &Some(b), &Some(c)) = (&a.as_ref(), &b.as_ref(), &c.as_ref()) {
    …
}

As for.or(), I think you mistook it for the boolean or || (from the code you wrote), which it is not. You can check out it's documentation.

1 Like

Thanks! The only reason I didn't try that was - I think - I'd assumed that any method on the option was causing the error, not just the or. (I was also wrong about what or does, so thanks for pointing that out too!)

1 Like

If they are required to be Some why do you not just not use Option and require the callee to unwrap?

fn f(a: String, b: String, c: String) -> Option<String> {
    Some(format!("{}:{}:{}", a, b, c))
}

fn main() {
    let together = f(Some("a".into()).unwrap(), Some("b".into()).unwrap(), Some("c".into()).unwrap());
    println!("{:?}", together);
}

playpen

1 Like

You don't need to unwrap within that if let block. a, b, c will extracted from their Option wrappers, so you can use them as bare values.

1 Like

Indeed, it's way nicer to not unwrap at all, so you have the compiler guarantee that you did the check properly!

1 Like

Thanks guys, those last two really helped clarify for me what makes sense here.