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.