Here's an example that splits a string and parses each item, putting it into a tuple whose size is known at compile time.
use std::str::FromStr; fn main() { let some_str = "123,321,312"; let num_pair_str = some_str.split(',').collect::<Vec<&str>>(); if num_pair_str.len() == 3 { let num_pair: (i32, i32, i32) = ( i32::from_str(num_pair_str[0]).expect("failed to parse number"), i32::from_str(num_pair_str[1]).expect("failed to parse number"), i32::from_str(num_pair_str[2]).expect("failed to parse number"), ); println!("Tuple {:?}", num_pair); } } Is there a way to avoid repetition parsing the numbers?
This is an example of what it might look like if Rust supported Python-like comprehensions:
let num_pair: (i32, i32, i32) = ( i32::from_str(num_pair_str[i]).expect("failed to parse number") for i in 0..3 ); Is it possible to declare the tuple in a way that expands the vector?
3 Answers
EDIT: A simple one-liner solution: collect_tuple of the popular itertools crate.
let iter = 1..3; let (x, y) = iter.collect_tuple().unwrap(); // yeah! Original answer
If you want better IDE type hinting, macros may not be a perfect solution. Here is my attempt:
fn tuple1<T>(a: &[T]) -> (&T) { (&a[0]) } fn tuple2<T>(a: &[T]) -> (&T, &T) { (&a[0], &a[1]) } fn tuple3<T>(a: &[T]) -> (&T, &T, &T) { (&a[0], &a[1], &a[2]) } fn tuple4<T>(a: &[T]) -> (&T, &T, &T, &T) { (&a[0], &a[1], &a[2], &a[3]) } fn tuple5<T>(a: &[T]) -> (&T, &T, &T, &T, &T) { (&a[0], &a[1], &a[2], &a[3], &a[4]) } fn tuple6<T>(a: &[T]) -> (&T, &T, &T, &T, &T, &T) { (&a[0], &a[1], &a[2], &a[3], &a[4], &a[5]) } 0You can't use Python-like list comprehension, as Rust doesn't have it. The closest thing is to do it explicitly via another iterator. You can't directly collect into a tuple, so you need another explicit step to convert the vector:
use std::str::FromStr; fn main() { let some_str = "123,321,312"; let num_pair_str = some_str.split(',').collect::<Vec<_>>(); if num_pair_str.len() == 3 { let v = num_pair_str.iter().map(|s| i32::from_str(s).expect("failed to parse number")) .collect::<Vec<_>>(); let num_pair: (i32, i32, i32) = (v[0], v[1], v[2]); println!("Tuple {:?}", num_pair); } } If you want to avoid the intermediate vectors you can do something like the following:
use std::str::FromStr; fn main() { let some_str = "123,321,312"; let it0 = some_str.split(','); if it0.clone().count() == 3 { let mut it = it0.map(|s| i32::from_str(s).expect("failed to parse number")); let num_pair: (i32, i32, i32) = (it.next().unwrap(), it.next().unwrap(), it.next().unwrap()); println!("Tuple {:?}", num_pair); } } You can declare a trait with a method similar to Iterator::collect and implement it to collect to various tuples sizes:
fn main() { // Example with some simplifications // Note that there is no extra allocation let num_pair: (i32, i32, i32) = "123,321,312" .split(',') .map(|s| s.parse().expect("an i32")) .try_collect() .expect("a 3-tuple of i32"); assert_eq!(num_pair, (123, 321, 312)); } trait TryCollect<T> { fn try_collect(&mut self) -> Option<T>; } macro_rules! impl_try_collect_tuple { () => { }; ($A:ident $($I:ident)*) => { impl_try_collect_tuple!($($I)*); impl<$A: Iterator> TryCollect<($A::Item, $($I::Item),*)> for $A { fn try_collect(&mut self) -> Option<($A::Item, $($I::Item),*)> { let r = (try_opt!(self.next()), // hack: we need to use $I in the expasion $({ let a: $I::Item = try_opt!(self.next()); a}),* ); Some(r) } } } } macro_rules! try_opt { ($e:expr) => (match $e { Some(e) => e, None => return None }) } // implement TryCollect<T> where T is a tuple with size 1, 2, .., 10 impl_try_collect_tuple!(A A A A A A A A A A); Other examples:
fn main() { let mut iter = (0..7).into_iter(); let (a, b, c) = iter.try_collect().unwrap(); assert_eq!((a, b, c), (0, 1, 2)); let (d, e) = iter.try_collect().unwrap(); assert_eq!((d, e), (3, 4)); let (f,) = iter.try_collect().unwrap(); assert_eq!(f, 5); let a: Option<(u32, u32)> = iter.try_collect(); assert_eq!(None, a); } 0