rustrayonrust-itertools

Unexpected "method not found" compiler error


This question arises from my use (in a toy project to learn Rust) of cartesian_product from itertools together with into_par_iter from Rayon. My question is less about this particular code and more a question about reading rustc error messages, and the Rust library documentation.

This works as expected:

fn main() {
    let it = 0..15;
    it.into_par_iter().for_each(|x| println!("{:?}", x));
}

But the code below fails to compile with the error indicated. The documentation for the Product returned by cartesian_product includes an implementation of Iterator, so I would have expected the into_par_iter method call to type check, but such is not the case.

Here's the failing code and the resulting error message:

fn main() {
    let it = (0..15).cartesian_product(0..8);
    it.into_par_iter().for_each(|x| println!("{:?}", x));
}

Compiling iters v0.1.0 (D:\rust\iters)
error[E0599]: no method named `into_par_iter` found for struct `itertools::adaptors::Product<std::ops::Range<{integer}>, std::ops::Range<{integer}>>` in the current scope
   --> src\main.rs:8:8
    |
8   |       it.into_par_iter().for_each(|x| println!("{:?}", x));
    |          ^^^^^^^^^^^^^ method not found in `itertools::adaptors::Product<std::ops::Range<{integer}>, std::ops::Range<{integer}>>`
    |
   ::: D:\rust\dot.cargo\registry\src\github.com-1ecc6299db9ec823\itertools-0.9.0\src\adaptors\mod.rs:288:1
    |
288 | / pub struct Product<I, J>
289 | |     where I: Iterator
290 | | {
291 | |     a: I,
...   |
294 | |     b_orig: J,
295 | | }
    | | -
    | | |
    | |_doesn't satisfy `_: rayon::iter::IntoParallelIterator`
    |   doesn't satisfy `_: rayon::iter::ParallelIterator`
    |
    = note: the method `into_par_iter` exists but the following trait bounds were not satisfied:
            `itertools::adaptors::Product<std::ops::Range<{integer}>, std::ops::Range<{integer}>>: rayon::iter::ParallelIterator`
            which is required by `itertools::adaptors::Product<std::ops::Range<{integer}>, std::ops::Range<{integer}>>: rayon::iter::IntoParallelIterator`
            `&itertools::adaptors::Product<std::ops::Range<{integer}>, std::ops::Range<{integer}>>: rayon::iter::ParallelIterator`
            which is required by `&itertools::adaptors::Product<std::ops::Range<{integer}>, std::ops::Range<{integer}>>: rayon::iter::IntoParallelIterator`
            `&mut itertools::adaptors::Product<std::ops::Range<{integer}>, std::ops::Range<{integer}>>: rayon::iter::ParallelIterator`
            which is required by `&mut itertools::adaptors::Product<std::ops::Range<{integer}>, std::ops::Range<{integer}>>: rayon::iter::IntoParallelIterator`


Solution

  • itertools::Itertools::cartesian_product returns a value of type itertools::Product, which implements std's Iterator.

    However, Rayon can't just work with any type that implements Iterator - it must also implement rayon::ParallelIterator. Rayon happens to provide implementations of ParallelIterator for most std iterators, but it can't implement them for structs in another crate (like itertools) without depending on that crate. Similarly, itertools couldn't implement rayon::ParallelIterator for its types without depending on rayon.

    Instead, you can duplicate the functionality of Itertools::cartesian_product yourself using Rayon's API:

    use rayon::iter::{ParallelIterator, IntoParallelIterator};
    
    fn main() {
        (0..15).into_par_iter()
            .flat_map(|i| (0..8).into_par_iter().map(move |j| (i, j)))
            .for_each(|x| println!("{:?}", x));
    }
    

    Alternatively, you can start with an iterator of length (15 * 8) and then use division and remainder to break it down into tuples:

    use rayon::iter::{ParallelIterator, IntoParallelIterator};
    
    fn main() {
        let it = (0 .. 15 * 8).into_par_iter().map(|x| (x / 8, x % 8));
        it.for_each(|x| println!("{:?}", x));
    }