anderspitman
anderspitman

Reputation: 10520

Is there any way to unpack an iterator into a tuple?

Is there any way to accomplish something like the following:

let v = vec![1, 2, 3];
let (a, b) = v.iter().take(2);

Such that a = 1 and b = 2 at the end?

I know I could just use a vector but I would like to have named variables.

Upvotes: 42

Views: 29846

Answers (4)

gcp
gcp

Reputation: 764

The itertools crate has methods like tuples and next_tuple that can help with this.

use itertools::Itertools; // 0.9.0

fn main() {
    let v = vec![1, 2, 3];
    let (a, b) = v.iter().next_tuple().unwrap();

    assert_eq!(a, &1);
    assert_eq!(b, &2);
}

Upvotes: 33

helios35
helios35

Reputation: 1637

This may not be exactly what you asked for, but I suppose you rarely want to convert an arbitrarily large vector to a tuple anyway. If you just want to extract the first few elements of a vector into a tuple, you can do so using slice pattern matching:

fn main() {
    let v = vec![1, 2, 3];
    let (a, b) = match &v[..] {
        &[first, second, ..] => (first, second),
        _ => unreachable!(),
    };
    assert_eq!((a, b), (1, 2));
}

Upvotes: 26

AmigoNico
AmigoNico

Reputation: 6852

gcp is on the right track; his answer seems like the correct one to me.

I'm going to give a more compelling example, though, since the OP seemed in a comment to wonder whether what he asked for is even worthwhile ("I can't think of a good enough reason for this functionality to be possible."). Check out the Person::from_csv function below:

use itertools::Itertools;

#[derive(Debug)]
struct Person<'a> {
    first: &'a str,
    last:  &'a str,
}

impl<'a> Person<'a> {
    // Create a Person from a str of form "last,first".
    fn from_csv(s: &'a str) -> Option<Self> {
        s.split(',').collect_tuple().map(
            |(last, first)| Person { first, last }
        )
    }
}

fn main() {
    dbg!(Person::from_csv("Doe"));          // None
    dbg!(Person::from_csv("Doe,John"));     // Some(...)
    dbg!(Person::from_csv("Doe,John,foo")); // None
}

It takes the Iterator produced by split and collects the results into a tuple so that we can match and destructure it. If there are too many or too few commas, you won't get a matching tuple. This code is clean because collect_tuple lets us use pattern matching and destructuring.

Here it is in the playground.

Upvotes: 2

Jesko H&#252;ttenhain
Jesko H&#252;ttenhain

Reputation: 1307

I wrote this ugly recursive macro that converts a Vec to a tuple because I wanted to learn something about macros.

macro_rules! tuplet {
    { ($y:ident $(, $x:ident)*) = $v:expr } => {
        let ($y, $($x),*) = tuplet!($v ; 1 ; ($($x),*) ; ($v[0]) );
    };
    { $v:expr ; $j:expr ; ($y:ident $(, $x:ident)*) ; ($($a:expr),*) } => {
        tuplet!( $v ; $j+1 ; ($($x),*) ; ($($a),*,$v[$j]) )
    };
    { $v:expr ; $j:expr ; () ; $accu:expr } => {
        $accu
    }
}

I am new to this and probably very bad at it, so there's most likely a better way to do it. This is just a proof of concept. It allows you to write:

fn main() {
    let v = vec![1, 2, 3];
    tuplet!((a, b, c) = v);

    assert_eq!(a, 1);
    assert_eq!(b, 2);
    assert_eq!(c, 3);
}

Somewhere in that macro definition you find the part $v[$j], which you could replace by $v.nth($j) if you want to use it for iterators.

Upvotes: 6

Related Questions