Why return nested closure fail compile

Commented code can compile successfully,but I can't figure out the difference between them.

type HookBuilder = Box<dyn Fn() -> HookFunc + Send + Sync>;
type HookFunc =  Box<dyn Fn(&str)>;

pub fn consumer(make_hook_func: HookBuilder) {
    let f = make_hook_func;
    let s = "test".to_string();
    f()(s.as_str())
}

static OBJ: Owner = Owner {data: "owned data"};

struct Owner {
    data: &'static str
}

pub fn main() {
    // consumer(Box::new(|| OBJ.make_func()))
    consumer(OBJ.make_func())
}

impl Owner {
    // fn make_func(&self) -> HookFunc {
    //     let data: &'static str = self.data;
    //     if cfg!(debug_assertions) {
    //         Box::new(move |s| println!("{}-{}", s, data))
    //     } else {
    //         Box::new(|_| {println!("123")})
    //     }
    // }
    
    fn make_func(&self) -> HookBuilder {
        let data: &'static str = self.data;
        let f:HookFunc = if cfg!(debug_assertions) {
            Box::new(move |s| println!("{}{}", s, data))
        } else {
            Box::new(|_| {println!("123")})
        };
        Box::new(|| f)
    }
}


(Playground)

You can't put a non-Send HookFunc inside a HookBuilder because it requires it to be Send.

But why commented code compile? I think they are the same.

The commented code doesn't try to put a HookFunc inside a HookBuilder.

Next to the send/sync issues there’s also an issue that you cannot create an Fn like this with Box::new(|| f) since you can only move out of f once. But this works:

    fn make_func(&self) -> HookBuilder {
        let data: &'static str = self.data;
        if cfg!(debug_assertions) {
            Box::new(move || Box::new(move |s| println!("{}{}", s, data)))
        } else {
            Box::new(|| Box::new(|_| {println!("123")}))
        }
    }

Sorry, still not get it. How? The commented code make a HookFunc first, then put it into a Box which is the HookBuilder, and the builder is consumed finally.

By writing Box::new(|| f) with a type hint of HookBuilder, you create a HookBuilder. Since a HookBuilder has a Send requirement, this requirement is imposed on what you put in the box.

1 Like

Great, that make sense now. BTW, does it documented somewhere, about the imposed behavior?

There’s this paragraph in the reference about closures implementing Send/Sync and this information in the docs about Send/Sync impl for Box. For completeness, here’s the reference chapter about trait objects.

A few quotes:

Because captures are often by reference, the following general rules arise:

  • A closure is Sync if all captured variables are Sync .
  • A closure is Send if all variables captured by non-unique immutable reference are Sync , and all values captured by unique immutable or mutable reference, copy, or move are Send .
  • A closure is Clone or Copy if it does not capture any values by unique immutable or mutable reference, and if all values it captures by copy or move are Clone or Copy , respectively.
impl<T: ?Sized> Send for Box<T>
where
   T: Send, 

impl<T: ?Sized> Sync for Box<T>
where
   T: Sync, 

A trait object is an opaque value of another type that implements a set of traits. The set of traits is made up of an object safe base trait plus any number of auto traits.

Trait objects implement the base trait, its auto traits, and any supertraits of the base trait.

1 Like