Feature request: expand zip() and unzip() methods on iterator to N-tuple and N iterators

zip and unzip can only work with pairs (converting between 2-element tuples and 2 iterators). Let's expand it to arbitrary number rather than 2 (conversion between N-sized tuples and N iterators).

Example use case:

// Let's say I'm writing a unit test for a function that returns this vec:
fn tupler() -> Vec<(u64, &str, u64, u64, bool)>{
    vec![
        (1, "2", 3, 4, false), 
        (1, "lol", 5, 10, true), 
        ..., 
        (100, "hello", 82, 47, true)
    ]
} 
#[cfg(test)]
mod tests {
    #[test]
    fn tuple_test() {
        let tuples_vec = super::tupler();

        // And let's also say I have real/correct data to compare the 
        // function output against, and this real/correct data comes 
        // in the form of 5 separate vectors:
        let vec1 = vec![1, 1, ..., 100];
        let vec2 = vec!["2", "lol", ..., "hello"];
        let vec3 = vec![3, 5, ..., 82];
        let vec4 = vec![4, 10, ..., 47];
        let vec5 = vec![false, true, ..., true];

        // would be awesome if I could somehow unzip the vectors of 
        // 5-sized tuples into 5 separate iterators and create an  
        // assertion for each iterator:
        let (func_val1, func_val2, func_val3, func_val4, func_val5) = 
            tuples_vec.iter().unzip();
        assert_eq!(func_val1, vec1);
        assert_eq!(func_val2, vec2);
        assert_eq!(func_val3, vec3);
        assert_eq!(func_val4, vec4);
        assert_eq!(func_val5, vec5);
    }
}

An interesting thing about unzip, is that it can produce two different container types, ie Vec<1>, HashSet<2>.

Which means, that to expand unzip to n-tuples without breaking existing code we'd need to have variadic generics first.

Another point is that this property already makes using unzip a bit less than ergonomic, and I shiver a bit imagining a type annotation nightmare it would become with larger tuples.

Ideally, we'd have GADTs (might be using the wrong name for the feature here) in addition to variadic, so we could have something like:

fn unzip_n<C>(&self) -> (C<T> for T in Self::Item) { ... }

let _ = iter.unzip_n::<Vec>();

Edit: Or maybe implementing Extend<T> for () and using it with variadic inzip to filter out some members of iterator item could be neat.

1 Like

It's an obvious request, but it's waiting on variadic generics.

For now, you can use things like izip in itertools - Rust.

1 Like

You can kinda get the same result by nesting the tuples one inside the other Rust Playground

This is thanks to Extend and Default being implemented for tuples of types that implement Extend and Default, see for example Extend in std::iter - Rust

The stdlib could expand this to other tuple sizes, but this would have to be done manually, or using a macro to generate the implementations for all tuple sizes up until a certain point. It would not possible to support arbitrary tuple sizes, for that variadics are needed.


Also note that unzip doesn't create multiple iterators, because it has no reasonable way to do so. You can advance the subiterators one by one separately, but the original iterator can only be advanced all at once. Hence, unzip allows you to unzip into multiple collections, not iterators, more like collect does (but a little less efficiently).


This is only possible with nested tuples, i.e. (T, (U, V)), but not with something like (T, U, V), which is what op is asking. But also, existing code is already ambiguous due to multiple types satisfying Extend<(U, V)>, so it already needs type annotations.

2 Likes

perhaps u could define a variadic unzip() and zip() by internally translating it into the nested tuples that u presented in Rust Playground?

The problem is how to make them variadic in the first place (you can't with the current language features), not how they are internally implemented.

The 'ol "implement it up to 12-tuples with macros" to the rescue!

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