Convert tuple(option, option) into Option<tuple>

If I had a vector, I would do this:

let t = vec![Some(1), Some(2), Some(3)];
let n: Option<Vec<isize>> = t.into_iter().collect();

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

But I cannot do the same with a tuple because I can't iterate.

let t = (1, "two", 3);

let tup = Option<(isize, String, isize)> = t.something();

Is there any easy way to do this?

Here's one way:

let tup: (Option<u32>, Option<String>, Option<&str>)
    = (Some(1), None, Some("test"));
    
let opt_tup = match tup {
    (Some(a), Some(b), Some(c)) => Some((a, b, c)),
    _ => None,
};
1 Like

Yes, but this is fairly long winded. I could put it in a generic function, but I'd have to have a different one for each tuple length.

With the experimental try syntax (currently nightly-only), there's a slightly shorter way to write it:

try { (t.0?, t.1?, t.2?) }

Playground

As you mention, it's hard to provide a fully-generic solution because we don't yet have generics over different tuple lengths. A library could use a macro to generate implementations up to some max length.

5 Likes

First I would review my code, looks like you doing something you should not do. Later create a macro to convert a tuple of N into a Vec, then copy paste macros increasing the tuple size until the amount I need.

On the note of macros, you could make a macro to do this (in large part thanks to hygienic macros)! playground (The only downside is that you can't pass in a tuple directly, you must pass in a list of options)

My plan was more to use macro to create the functions. At least I think is what people are doing with code like this:

define_open!{A}
define_open!{A, B}
define_open!{A, B, C}
define_open!{A, B, C, D}
define_open!{A, B, C, D, E}
.
.
.

https://docs.rs/specs/0.10.0/src/specs/join.rs.html#69-90

I could be wrong, I never try to understand those macros.

"First I would review my code, looks like you doing something you should not do."

So, I have two bits of XML -- and IRI and a data literal. The IRI I need to find a type definition for, so I can then convert it into another type, and the literal needs to be converted to a int; both of these can fail. If they both succeed, I want to produce a struct of a third type.

Imagine I have an infix expression evaluator.

let t = ("1", "+", "1");
let ct = (to_int(t.0), to_op(t.1), to_int(t.2))
let opt_expr = convert(ct);
// Should return either 2 in this case, but could return "None".
opt_expr.map(|expr| evaluate(expr))

Hard to give a shorter example at the moment.

If you are returning Option from your function, you can use the question mark operator:

let t = ("1", "+", "1");
let expr = (to_int(t.0)?, to_op(t.1)?, to_int(t.2)?);
evaluate(expr)

Hmm, yes, you may be right. Instead of tuples, I can chain together calls to closures with and_then. A slightly noisy example, but....

    let x = "1";
    let y = "2";

    println!("{:?}",
             x.parse::<i32>().ok().and_then(
                 |xN| y.parse::<i32>().ok().and_then(
                     |yN| Some(xN + yN)
                 )
             )
    );

Adds the numbers, or returns None. Does not need to use a tuple to do the "has everything succeded" checked, and passes the intermediate results through the closures.

Or a simpler solution still, is to use the ? operator. It's slightly complex because I want an expression not a function, so I need to use closures.

playground

The inline solution ends up in a lot of hieroglyphics but is very succinct (or perhaps terse).

I ended up macro'ing the whole thing, doing something like @mbrubeck's try based solution, but adding in the return of an Option. I think this is a nice solution without so many brackets and symbols.

macro_rules! some {
    ($body:expr) => {
        (|| {Some($body)})()
    }

}

fn main() {

    let x = "1".parse::<i32>().ok();
    let y = "2".parse::<i32>().ok();

    println!("{:?}", || -> Option<i32>{Some(x? + y?)}());

    println!("{:?}", some!{x? + y?});
}

Thank you to everyone for your help!

Note that the try{} includes wrapping the result in Some() so also gives an Option:

    let x = "1".parse::<i32>().ok();
    let y = "2".parse::<i32>().ok();
    let z: Option<_> = try { x? + y? };

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=98731774f2a9ce1acf802264612bdb38

Ah, thanks, hadn't realised that. That makes life easy, because when try hits release I can switch over naturally.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.