Option<T>, Option<U> -> Option<(T, U)>


#1

I often find myself wanting this method, am I alone?

impl<T> Option<T> {
    fn and_also<U>(self, other: Option<U>) -> Option<(T, U)> {
        match (self, other) {
            (Some(this), Some(that)) => Some((this, that)),
            _                        => None,
        }
    }
}

The shortest way to write this is this:

option.and_then(|this| other.map(|that| (this, that))

Often, this is the lead in to some sort of and_then or if let processing that requires both values, making the verbosity of the above rather unpleasant. Its also not easy to read if you don’t know it offhand, in my opinion, requiring reasoning about two higher order functions and closures within closures.

Is this pattern common or is it just my code? Would encapsulating this in a method be useful and worthwhile?

Other reasonable names for this method that occur to me are as_well_as and merge. And it also makes sense to include on Result.


#2

Other reasonable names for this method that occur to me are as_well_as and merge. And it also makes sense to include on Result.

These kind of operations are often called ‘zip’. Rust already uses this name for the same operation on iterators.


#3

Actually, you can even write this operation using the iterator version of zip:

let result = option.iter().zip(other).next();

Not sure that’s actually a good idea, though. :slight_smile:


#4

You probably want into_iter() to take it by value. Then with itertools this becomes just izip!(option, other).next().

When you want it for if let, you could instead pattern match both directly:

if let (Some(this), Some(that)) = (option, other) { /*...*/ }

#5

That’s true! And it occurs to me that another way to do this is to impl<T, U> Into<Option<(T, U)> for (Option<T>, Option<U>).


#6

Another would be catch { (option?, other?) }


#7

I’d like to see this implemented as a method on tuple cons cells. Hypothetical syntax follows:

trait TupleOfOptions<T: Tuple>: Tuple {
    fn all(self) -> Option<T>;
    fn any(self) -> Option<T::Dual>;
}

impl TupleOfOptions<()> for () {
    fn all(self) -> Option<()> {
        Some(())
    }

    fn any(self) -> Option<!> {
        None
    }
}

impl<H, T, U> TupleOfOptions<(H, ...T)> for (Option<H>, ...U)
    where T: Tuple,
          U: TupleOfOptions<T>
{
    fn all(self) -> Option<(H, ...T)> {
        let (h_opt, ...u) = self;
        match (h_opt, u.all()) {
            (Some(h), Some(t)) => Some((h, ...t)),
            _ => None,
        }
    }

    fn any(self) -> Option<(H | ...T::Dual)> {
        let (h_opt, ...u) = self;
        match (h_opt, u.any()) {
            (Some(h), _)    => Some((h | ...!)),
            (None, Some(t)) => Some((! | ...t)),
            _               => None,
        }
    }
}

Then you could use it on a tuple of any number of Options

if let Some((a, b, c)) = (a_opt, b_opt, c_opt).all() {
    ...
}