32

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?"

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Erik Vesteraas
  • 4,675
  • 2
  • 24
  • 37

8 Answers8

32

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)
Dogbert
  • 212,659
  • 41
  • 396
  • 397
  • 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. – Erik Vesteraas Nov 18 '15 at 18:00
  • 2
    Another option that's a little longer but maybe easier to follow: `a.and_then(|a| b.and_then(|b| Some(f(a, b))))` – Jack O'Connor Jul 02 '18 at 17:41
  • 1
    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);` – Lord of the Goo Mar 09 '23 at 17:58
22

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))
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
12

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)
Jack O'Connor
  • 10,068
  • 4
  • 48
  • 53
  • 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. – RubberDuck Jul 01 '18 at 14:57
  • 4
    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)`. – Jack O'Connor Jul 02 '18 at 17:38
8

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:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 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. – Erik Vesteraas May 20 '20 at 09:58
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)
Fuji
  • 28,214
  • 2
  • 27
  • 29
  • Your "option 2" is already present in [the highest-voted answer](https://stackoverflow.com/a/33779802/155423) – Shepmaster Jun 21 '19 at 14:47
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.

oli_obk
  • 28,729
  • 6
  • 82
  • 98
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);
Lord of the Goo
  • 1,214
  • 15
  • 31
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
Claudio Fsr
  • 106
  • 6