2

I want to share a function reference between threads but the Rust compiler says `dyn for<'r> std::ops::Fn(&'r std::string::String) -> std::string::String` cannot be shared between threads safely. I'm well informed about Send, Sync, and Arc<T> when sharing "regular" values between threads but in this case I can't understand the problem. A function has a static address during the runtime of the program, therefore I can't see a problem here.

How can I make this work?

fn main() {
    // pass a function..
    do_sth_multithreaded(&append_a);
    do_sth_multithreaded(&identity);
}

fn append_a(string: &String) -> String {
    let mut string = String::from(string);
    string.push('a');
    string
}

fn identity(string: &String) -> String {
    String::from(string)
}

fn do_sth_multithreaded(transform_fn: &dyn Fn(&String) -> String) {
    for i in 0..4 {
        let string = format!("{}", i);
        thread::spawn(move || {
            println!("Thread {}: {}", i, transform_fn(&string))
        });
    }
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
phip1611
  • 5,460
  • 4
  • 30
  • 57
  • 1
    I'm aware of this post. https://stackoverflow.com/questions/59442080/rust-pass-a-function-reference-to-threads But I still want to learn and understand why it's not working the way I thought it would. – phip1611 Jan 08 '20 at 13:12
  • 1
    Why do you want to use `&dyn Fn` instead of function pointers? Should `do_sth_multithreaded` work with closures, too? – Cerberus Jan 08 '20 at 13:14
  • It's not a requirement yet, no. Perhaps it could be in future. – phip1611 Jan 08 '20 at 13:17
  • I didn't know about "real" function pointers. It works with this function definition `fn do_sth_multithreaded(transform_fn: fn(&String) -> String)` - thanks! Invocation looks like this: `do_sth_multithreaded(identity);` – phip1611 Jan 08 '20 at 13:27

1 Answers1

6

A function has a static address during the runtime of the program, therefore I can't see a problem here.

That's nice for functions, but you're passing a &dyn Fn, and that could just as well be a closure or (in unstable Rust) a custom object implementing that trait. And this object might not have a static address. So you can't guarantee that the object will outlive the threads you spawn.

But that's not even what the compiler is complaining about (yet!). It's actually complaining that it doesn't know whether you're allowed to access the Fn from another thread. Again, not relevant for function pointers, but relevant for closures.

Here's a signature that works for your example:

fn do_sth_multithreaded(transform_fn: &'static (dyn Fn(&String) -> String + Sync))

Note the 'static lifetime bound and the Sync bound.

But while the static lifetime works for this case, it probably means you can't ever send closures. To make that work, you need to use a scoped thread system (for example, from the crossbeam crate) to make sure do_sth_multithreaded waits for the threads to finish before returning. Then you can relax the static lifetime bound.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Sebastian Redl
  • 69,373
  • 8
  • 123
  • 157