What is the diffrence between closure return by function and generated locally in rust?

Viewed 83

The following codes show two closures: c1, c2. c1 is returned by a function, c2 is generated locally. c1 can not be sent to a thread. Why?

fn gen_closure() -> Box<dyn Fn(&str)> {
    return Box::new(|s: &str| {
        println!("gen_closure: {}", s);
    });
}

fn main() {
    let c1 = gen_closure();
    let c2 = Box::new(|s: &str| {
        println!("main_closure: {}", s);
    });

    /* `dyn for<'r> Fn(&'r str)` cannot be sent between threads safely
    the trait `Send` is not implemented for `dyn for<'r> Fn(&'r str)`
    required because of the requirements on the impl of `Send` for `Unique<dyn for<'r> Fn(&'r str)>`
    required because it appears within the type `[closure@src/main.rs:13:24: 15:6]` */
    std::thread::spawn(move || {
        (c1)("c1");
    });

    /* This is okay */
    std::thread::spawn(move || {
        (c2)("c2");
    });
}
2 Answers

In Rust (as in e.g. C++) every closure has its own anonymous type.

Since c2 is a local, the compiler knows exactly what its concete type is, and thus can know all its traits, including that it's Send (because it closes over nothing that's !Send... since it's not closing over anything).

However c1 is returned as dyn Fn, meaning as far as the compiler is concerned the only trait it can rely on is Fn. The compiler only has local visibility, and that's what you're telling it to rely on.

The way to make it sendable is to assert and guarantee that it is:

fn gen_closure() -> Box<dyn Fn(&str) + Send>

In that case, the caller can rely on this contract, and the callee will not compile if the returned function is not actually Send e.g.

fn gen_closure() -> Box<dyn Fn(&str) + Send> {
    let rc = std::rc::Rc::new(1);
    return Box::new(move |s: &str| {
        println!("gen_closure: {} {}", s, rc);
    });
}

=>

error[E0277]: `Rc<i32>` cannot be sent between threads safely
 --> src/main.rs:3:12
  |
3 |        return Box::new(move |s: &str| {
  |  _____________^________-
  | | ____________|
  | ||
4 | ||         println!("gen_closure: {} {}", s, rc);
5 | ||     });
  | ||_____-^ `Rc<i32>` cannot be sent between threads safely
  | |______|
  |        within this `[closure@src/main.rs:3:21: 5:6]`
  |
  = help: within `[closure@src/main.rs:3:21: 5:6]`, the trait `Send` is not implemented for `Rc<i32>`
  = note: required because it appears within the type `[closure@src/main.rs:3:21: 5:6]`
  = note: required for the cast to the object type `dyn for<'r> Fn(&'r str) + Send`

To complement Masklinn's answer, I want to give another example:

If you change the return type to be impl Fn(&str), then it will also work. Because the function will be instantiated for the concrete type, the returned type will be known to be Send.

fn gen_closure() -> impl Fn(&str) {
    return |s: &str| {
        println!("gen_closure: {}", s);
    };
}
Related