Fn(&NotSendNorSync) is !Sync... why?

Surprised today to find out this doesn't compile:

use rayon::prelude::*;

// Pretend we have a type that is neither Send nor Sync.
struct NotThreadSafe(*const ());

fn foo(f: &impl Fn(&NotThreadSafe) -> i32) -> impl ParallelIterator<Item = i32> {
    let huge_container = vec![1, 2, 3, 4, 5, 6, 7, 8];
    huge_container.par_iter().map(|x| {
        let n = NotThreadSafe(std::ptr::null());
        f(&n)
    })
}

Gives the error:

    Checking playground v0.1.0 (/home/xxxxx/playground)
error[E0277]: `impl Fn(&NotThreadSafe) -> i32` cannot be shared between threads safely
   --> src/lib.rs:14:35
    |
14  |       huge_container.par_iter().map(|x| {
    |                                 --- ^--
    |                                 |   |
    |  _______________________________|___within this `[closure@src/lib.rs:14:35: 14:38]`
    | |                               |
    | |                               required by a bound introduced by this call
15  | |         let n = NotThreadSafe(std::ptr::null());
16  | |         f(&n)
17  | |     })
    | |_____^ `impl Fn(&NotThreadSafe) -> i32` cannot be shared between threads safely
    |
    = note: required because it appears within the type `&impl Fn(&NotThreadSafe) -> i32`
note: required because it's used within this closure

I can understand that a closure that captures something !Sync should not be Sync, but I don't understand why taking an argument by reference to one should make it so. I don't see any safety issue at least in my example code. If this was allowed could I somehow put it to nefarious purposes? :slight_smile:

It's nothing about closure arguments. Try it with &impl Fn() -> i32 with no arguments and you can see it fails with same errors. If you want closures to be Sync you need to specify that bound.

fn foo(f: &impl Fn(&NotThreadSafe) -> i32 + Sync) -> ...
5 Likes

The argument type is entirely irrelevant, it’s the missing + Sync bound on the generic closure type itself.

1 Like

doh, thanks