How does one zip three or more iterators into a flat tuple in Rust?

written


While working on implementing a virtual machine for an IOI task alongside my friend Jair, I showed him some code that enabled the slick implementation of a bitwise AND over two registers, in this case arrays of booleans:

let and_result: Vec<bool> = registers[x]
.iter()
.zip(registers[y])
.map(|(x, y)| *x && y)
.collect();

He then wondered if one could write code like this:

let a = vec!["a", "b", "c"];
let b = vec!["x", "y", "z"];
let c = vec!["l", "m", "n"];

let result = a
.iter()
.zip(b)
.zip(c)
.map(|(a, b, c)| {})
.collect();

in order to get a zip iterator that outputs length-three tuples.

However, this isn’t the case; instead of a tuple of three strings, you’ll get a tuple with two members: a tuple of two strings and a string (specifically ((&&str, &str), &str)).

let result = a.iter().zip(b).zip(c).collect::<Vec<_>>();
// Also note that the first element in the inner tuple is double-referenced.
assert_eq!(result, vec![((&"a", "x"), "l"), ((&"b", "y"), "m"), ((&"c", "z"), "n")])

It is a bit of a shame that the tuple isn’t flattened, nor is there a way to create a zip iterator with longer tuples in the standard library. In Python, one can simply do:

a = ["a", "b", "c"]
b = ["x", "y", "z"]
c = ["l", "m", "n"]

assert list(zip(a, b, c)) == [('a', 'x', 'l'), ('b', 'y', 'm'), ('c', 'z', 'n')]

One can replicate this behavior with the izip! macro from the itertools crate.

use itertools::izip;

let result: Vec<_> = izip!(a, b, c).collect();
// The double reference is nowhere to be seen!
assert_eq!(result, [("a", "x", "l"), ("b", "y", "m"), ("c", "z", "n")])

The behavior of itertools::izip may one day be possible in the Rust standard library without macros with variadic generics; see the draft RFC for more information.