Using Rayon with trait objects

I'm pretty new to Rust, but the other day I was trying to paralellize my code with Rayon. Here's a simplified version of it (assumes rayon 1.5.0 is in deps):

use rayon::prelude::*;

trait MyTrait {
    fn foo(&self) -> i32;
}

struct MyStruct {
    number: i32,
}

impl MyTrait for MyStruct {
    fn foo(&self) -> i32 {
        self.number
    }
}

fn new_struct(number: i32) -> Box<dyn MyTrait> {
    Box::new(MyStruct { number }) as Box<dyn MyTrait>
}

fn main() {
    let vec: Vec<Box<dyn MyTrait>> = vec![new_struct(1), new_struct(2), new_struct(3)];
    let result = vec.into_par_iter().map(|item| item.foo()).sum();

    assert_eq!(6, result);
}

It fails with the folowing message:

error[E0599]: no method named `par_iter` found for struct `Vec<Box<dyn MyTrait>>` in the current scope
   --> src/main.rs:23:22
    |
23  |       let result = vec.par_iter().map(|item| item.foo()).sum();
    |                        ^^^^^^^^ method not found in `Vec<Box<dyn MyTrait>>`
    |
    = note: the method `par_iter` exists but the following trait bounds were not satisfied:
            `&Vec<Box<dyn MyTrait>>: IntoParallelIterator`
            which is required by `Vec<Box<dyn MyTrait>>: rayon::iter::IntoParallelRefIterator`
            `&[Box<dyn MyTrait>]: IntoParallelIterator`
            which is required by `[Box<dyn MyTrait>]: rayon::iter::IntoParallelRefIterator`

Ultimately after searching Rayon's issues for "trait bounds were not satisfied" and some googling, I found a post on SO called Sending trait objects between threads in Rust.

The fix is to replace all instances of Box<dyn MyTrait> with Box<dyn MyTrait + Send>. After that the code seems to work just fine.

It took me way too long to figure that out, so I'm posting it here in case it helps others. :wink: The compiler message was a bit unclear, but if I looked at the docs for rayon::iter::ParallelIterator, then I'd discover sooner that the associated type for the iterator must be type Item: Send.

2 Likes

For Vec<T>, into_par_iter() and par_iter_mut() will both require T: Send (respectively for T itself or &mut T: Send), whereas par_iter() requires T: Sync to satisfy &T: Send. You are correct to use dyn Trait + Send for the former, or you might want + Sync for the latter, or both!

It would be nice if the compiler note went a little further on the trait bounds to say that they would implement IntoParallelIterator if Send/Sync were satisfied, but that might be hard for it to automatically deduce.

The standard library uses an unstable attribute #[rustc_on_unimplemented] to provide targeted messages in some cases, but I don't know of any mechanisms available to stable crates.

2 Likes

Ah, this explains why I had problems with coming up with the simplified version of my code. :smiley: In the original code, I used iter_mut, but in the context of the simplified version, iter_mut was completely unnecessary. But when I used iter and then par_iter, MyTrait + Send stopped working. Now I know why. :wink: Thanks!

Totally. At the beginning I wanted to report this as an issue in Rayon's issue tracker. I saw this as an opportunity to make the error message a bit more clear or maybe add something to the FAQ, but then I realized:

  1. They probably don't have much control over the message. (ah, I just realized you're in the Rayon org on GitHub :smiley:)
  2. I felt that part of the problem was just that I had too little Rust knowledge. I glanced over the docs for the IntoParallelIterator and I didn't know what I was looking for.

But the compiler could definitely do a better job at pointing me in the right direction. :wink:

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.