Is there any built in way to "combine" two Options?
Asked Answered
S

8

33

In the following sample program, is there any way I could avoid having to define map2?

fn map2<T, U, V, F: Fn(T, U) -> V>(f: F, a: Option<T>, b: Option<U>) -> Option<V> {
    match a {
        Some(x) => match b {
            Some(y) => Some(f(x, y)),
            None => None,
        },
        None => None,
    }
}

fn main() {
    let a = Some(5);
    let b = Some(10);
    let f = |a, b| {
        a + b
    };
    let res = map2(f, a, b);
    println!("{:?}", res);
    // prints Some(15)
}

For people who also speak Haskell, I guess this question could also be phrased as "Is there any tool we can use instead of liftM2 in Rust?"

Scincoid answered 18/11, 2015 at 12:2 Comment(0)
C
33

I don't believe there's a direct function equivalent to liftM2, but you can combine Option::and_then and Option::map like this:

fn main() {
    let a = Some(5);
    let b = Some(10);
    let f = |a, b| {
        a + b
    };

    println!("{:?}", a.and_then(|a| b.map(|b| f(a, b))));
}

Output:

Some(15)
Corves answered 18/11, 2015 at 12:14 Comment(3)
Thanks, that's actually a pretty good solution for when you only need to do this once or twice. Probably still worth it to define the function in some cases, though.Scincoid
Another option that's a little longer but maybe easier to follow: a.and_then(|a| b.and_then(|b| Some(f(a, b))))Goodfellowship
I think this solution is outdated and less idiomatic then the one that uses the zip or zip_with (unstable at present time). For example: let sum = one.zip(two).map(|(a, b)| a + b); or let sum = one.zip_with(two, |a, b| a + b);Prime
C
26

As of Rust 1.46.0, you can use Option::zip:

fn map2<T, U, V, F: Fn(T, U) -> V>(f: F, a: Option<T>, b: Option<U>) -> Option<V> {
    match a.zip(b) {
        Some((x, y)) => Some(f(x, y)),
        None => None,
    }
}

This can be combined with Option::map, as shown in other answers:

fn map2<T, U, V, F: Fn(T, U) -> V>(f: F, a: Option<T>, b: Option<U>) -> Option<V> {
    a.zip(b).map(|(x, y)| f(x, y))
}
Cafard answered 16/9, 2020 at 17:47 Comment(1)
On nightly there is zip_with().Oleviaolfaction
V
13

I don't know if you can get down to one line (Edit: oh the accepted answer gets it down to one line nicely), but you can avoid the nested match by matching on a tuple:

let a = Some(5);
let b = Some(10);
let f = |a, b| {
    a + b
};
let res = match (a, b) {
    (Some(a), Some(b)) => Some(f(a, b)),
    _ => None,
};
println!("{:?}", res);
// prints Some(15)
Valer answered 3/10, 2017 at 1:3 Comment(2)
I find this to be incomplete. If a or b is None this returns None where it should return either Some(5) or Some(10) respectively.Psia
I'm not sure it should. Note that both the example in the original question, and the and_then+map example above, return None if either argument is None. In general, if the return type of f is different from the type of its arguments, it might not be possible to return anything other than None. That said, if you want the fallback in this case, you can replace the _ => None clause with _ => a.or(b).Goodfellowship
D
7
let num_maybe = Some(5);
let num_maybe2 = Some(10);
let f = |a, b| {
    a + b
};

Option 1

if let (Some(a), Some(b)) = (num_maybe, num_maybe2) {
    f(a, b)
}

Option 2

num_maybe.and_then(|a| num_maybe2.map(|b| f(a, b))

Option 3

[num_maybe, num_maybe2].into_iter().flatten().fold(0, f)
Decoration answered 21/6, 2019 at 4:47 Comment(1)
Your "option 2" is already present in the highest-voted answerCafard
C
7

You can use an immediately invoked function expression (IIFE) combined with the ? (try) operator:

fn main() {
    let a = Some(5);
    let b = Some(10);
    let f = |a, b| a + b;

    let res = (|| Some(f(a?, b?)))();

    println!("{:?}", res);
}

In the future, you can use try blocks:

#![feature(try_blocks)]

fn main() {
    let a = Some(5);
    let b = Some(10);
    let f = |a, b| a + b;

    let res: Option<_> = try { f(a?, b?) };

    println!("{:?}", res);
}

See also:

Cafard answered 19/5, 2020 at 18:35 Comment(1)
Well that's pretty neat. Even when it is worth it to define map2, Some(f(a?, b?)) is a much nicer implementation than the one I wrote back in 2015.Scincoid
G
6

You can use the fact that Options can be iterated over. Iterate over both options, zip them together, and map the resulting iterator over your function.

fn main() {
    let a = Some(5);
    let b = Some(10);
    let f = |(a, b)| {
        a + b
    };
    let res = a.iter().zip(b.iter()).map(f).next();
    println!("{:?}", res);
    // prints Some(15)
}

This required a modification of f, so the arguments are merged into a single tuple-argument. It would be possible without modifying f, by directly mapping over |args| f.call(args), but then you would have to specify the closure kind of f.

Gowon answered 18/11, 2015 at 12:48 Comment(2)
alternative: map(|(a,b)| f(a,b))Corunna
yes, I was trying to avoid repeating the function call. If you are repeating it, then @Dogbert's solution is the better one.Gowon
P
1

I stumbled upon this thread and didn't find the most obvious and straightforward one-liner solution based on zip.

let one = Some(1);
let two = Some(2);
let sum = one.zip(two).map(|(a, b)| a + b);
assert_eq!(sum, Some(3));

let two: Option<i32> = None;
let sum = one.zip(two).map(|(a, b)| a + b);
assert_eq!(sum, None);

There's also the zip_with variant which is marked as unstable right now.

let sum = one.zip_with(two, |a, b| a + b);
Prime answered 24/5, 2022 at 16:24 Comment(0)
P
0

One of several solutions presented, now with impl OptionExtension. Adapted from the solution presented by Shepmaster:

pub trait OptionExtension<T> {
    fn combine_with<U, R, F>(self, other: Option<U>, f: F) -> Option<R>
    where
        F: Fn(T, U) -> R;
    
    fn combine_with_sum<U, R>(self, other: Option<U>) -> Option<R>
    where
        T: std::ops::Add<U, Output = R>;

    fn combine_with_mul<U, R>(self, other: Option<U>) -> Option<R>
    where
        T: std::ops::Mul<U, Output = R>;
}

impl<T> OptionExtension<T> for Option<T> {
    fn combine_with<U, R, F>(self, other: Option<U>, f: F) -> Option<R>
    where
        F: Fn(T, U) -> R,
    {
        self.zip(other).map(|(x, y)| f(x, y))
    }

    fn combine_with_sum<U, R>(self, other: Option<U>) -> Option<R>
    where 
        T: std::ops::Add<U, Output = R>
    {
        let sum = |a, b| {a + b};
        self.combine_with(other, sum)
    }

    fn combine_with_mul<U, R>(self, other: Option<U>) -> Option<R>
    where 
        T: std::ops::Mul<U, Output = R>
    {
        let mul = |a, b| {a * b};
        self.combine_with(other, mul)
    }
}

Other operations can also be added, such as fn combine_with sub, div, ...

See the Rust Playground.

And the main() function:

fn main() {
    let a: Option<f64> = Some(5.0);
    let b: Option<f64> = Some(10.0);
    let c: Option<f64> = None;

    let result_sum_ab = a.combine_with_sum(b);
    let result_sub_ab = a.combine_with_sub(b);
    let result_mul_ab = a.combine_with_mul(b);
    let result_div_ab = a.combine_with_div(b);

    assert_eq!(result_sum_ab, Some(15.0));
    assert_eq!(result_sub_ab, Some(-5.0));
    assert_eq!(result_mul_ab, Some(50.0));
    assert_eq!(result_div_ab, Some(0.5));

    println!("result_sum_ab: {:?}", result_sum_ab);
    println!("result_sub_ab: {:?}", result_sub_ab);
    println!("result_mul_ab: {:?}", result_mul_ab);
    println!("result_div_ab: {:?}", result_div_ab);

    let result_sum_ac = a.combine_with_sum(c);
    let result_mul_ac = a.combine_with_mul(c);

    assert_eq!(result_sum_ac, None);
    assert_eq!(result_mul_ac, None);

    println!("result_sum_ac: {:?}", result_sum_ac);
    println!("result_mul_ac: {:?}", result_mul_ac);
}

The output:

result_sum_ab: Some(15.0)
result_sub_ab: Some(-5.0)
result_mul_ab: Some(50.0)
result_div_ab: Some(0.5)
result_sum_ac: None
result_mul_ac: None
Pentheus answered 17/7, 2023 at 21:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.