1

I'm trying to write a function parallelize that takes as an argument a function B and returns a function C, where C returns a handle to a thread which is running B. I understand this may be clunky or ridiculous; it's mostly as a learning project. The higher level goal is to implement a parallel map function on iterators, where each element of the list will be mapped in a different thread (or potentially through a thread pool eventually). I have quickly realized that I'm in way over my head. Given what I have described, what should the type of parallelize be?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Sam Schick
  • 400
  • 3
  • 14

1 Answers1

7

Let's step through it piece by piece:

a function parallelize

fn parallelize() {
    unimplemented!()
}

Yes, that's a function alright.

argument a function B

I assume you'd like the function to be able to return something, so I'll use the generic type R.

fn parallelize<B, R>(function: B)
where
    B: FnOnce() -> R,
{
    unimplemented!()
}

returns a function C

Uh oh. You can't do this cleanly in Rust yet. That means we will return a boxed trait object instead

fn parallelize<B, R>(function: B) -> Box<FnOnce()>
where
    B: FnOnce() -> R,
{
    unimplemented!()
}

where C returns a handle to a thread which is running B

use std::thread::JoinHandle;

fn parallelize<B, R>(function: B) -> Box<FnOnce() -> JoinHandle<R>>
where
    B: FnOnce() -> R,
{
    unimplemented!()
}

And there's your signature. All done here!

.....

Of course, there's more to do. For example, you didn't state when the thread should actually start. That means I get to pick, and I pick the easiest way — the thread is started when the caller asks for the handle.

use std::thread::{self, JoinHandle};

fn parallelize<B, R>(function: B) -> Box<FnOnce() -> JoinHandle<R>>
where
    B: Send + FnOnce() -> R + 'static,
    R: Send + 'static,
{
    Box::new(|| thread::spawn(function))
}

I guess you could start it immediately, as well:

use std::thread::{self, JoinHandle};

fn parallelize<B, R>(function: B) -> Box<FnOnce() -> JoinHandle<R>>
where
    B: Send + FnOnce() -> R + 'static,
    R: Send + 'static,
{
    let handle = thread::spawn(function);
    Box::new(|| handle)
}

I don't know why you'd do the second one; in that case you might as well just return the handle directly. And if you do that, you might as well just call thread::spawn directly.

Oh, I suppose I couldn't slip those extra trait bounds past you, could I? When calling thread::spawn, you have to ensure that nothing you pass it will stop being valid before the thread exits (thus 'static) and is safe to transfer between threads (thus Send).

The higher level goal is to implement a parallel map function on iterators

That exists and is called Rayon.


See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366