Trying to use a nested for loop, works perfectly in most other languages, however in Rust, it is not as straightforward due to the borrow checker.
Problem
let vec_a = vec![1,2,3,4];
let vec_b = vec![5,6,7,8];
for a in vec_a {
for b in vec_b {
}
}
Discussion
The following code will not compile. The problem is that Vectors are not Copyable, so if we can only move it once.
So where is the vector being moved?
This line here: b in vec_b
which under the hood becomes b in vec_b.into_iter()
. The signature for into_iter which is defined in the IntoIterator trait:
fn into_iter(self) -> Self::IntoIter;
Note again, the problem is that into_iter
takes self, which consumes the value, in this case it is Vec
. Moreover, it does not matter if the underlying type is Copyable.
Solution one
Although Vectors are not Copyable, slices are. Since they are just a pointer and a length, they can be stored on the stack. Our code now looks like:
let vec_a = vec![1,2,3,4];
let vec_b = vec![5,6,7,8];
for a in vec_a {
for b in &vec_b {
}
}
If you want to be more verbose, you can write vec_b.as_slice
.
Discussion
If you do not need to own the items in the slice, then this solution works; you get a reference to an integer and not the actual integer.
Solution two
In most programming languages, you can “copy” anything, as in make an exact copy of something, which can be expensive or cheap depending on what it is you are copying. This notion of “copying” is called Clone
in Rust.
The next solution is to use clone since Vectors are Clonable.
let vec_a = vec![1,2,3,4];
let vec_b = vec![5,6,7,8];
for a in vec_a {
for b in vec_b.clone() {
}
}
Discussion
Each b
is now an integer and not a reference to vec_b
. For each iteration of vec_a
we create a fresh new copy of vec_b
. Whether this is expensive in practice depends on your vector size and it’s elements.
Solution three
If you really need to own the data structures in vec_b
, but you also want use the most optimal strategy. Then Iterators may be your best solution. Iterators in Rust are lazy and are generally easier to optimise by the compiler. First lets look at a solution using itertools, then a solution without it:
use itertools::Itertools; // 0.9.0
let vec_a = vec![1,2,3,4];
let vec_b = vec![5,6,7,8];
for a in vec_a.into_iter().cartesian_product(vec_b) {
}
The output of the for-loop will be:
(1,5)
(1,6)
(1,7)
(1,8)
(2,5)
(2,6)
(2,7)
(2,8)
(3,5)
(3,6)
(3,7)
(3,8)
(4,5)
(4,6)
(4,7)
(4,8)
Each element in vec_a
is combined with every element with vec_b
.
If you do not want to use itertools, the solution can be done with flat_map and map, however it is a bit more complicated.
The code looks like so:
let vec_a = vec![1,2,3,4];
let vec_b = vec![5,6,7,8];
for (a,b) in vec_a.into_iter().flat_map(|a| vec_b.iter().clone().map(move |b| (a, b))) {
println!("({},{})", a, b)
}
Discussion
As mentioned above, since Iterators are lazy, this may be the best solution for most usecases. Even moreso, if you can refactor your code to chain iterator methods and delay collecting into a Collection.
Note that the method that doesn’t use itertools, uses quite a few concepts and hinges on the fact that Integers are Copyable; clone only clones the iterator, not the value. If you want to Clone the underlying values, use cloned
and not clone
. Either way, there is nothing wrong with using the itertools dependency for simplicity!