Is there no way to (one way or another) create an owned version of a (Box of) closure in rust?

57 Views Asked by At

I want to write a FuncWrapper struct with a new function that takes a (Boxed) closure as parameter, and returns a decorated closure that just adds some boilerplate to the incoming one. But I also want the returned value to be "owned", to allow for the following (e.g.):

fn main() {                                                                                                                                                                                                                           
                                                                                                                                                                                                                                      
    let a: FuncWrapper<u32>;                                                                                                                                                                                                          
                                                                                                                                                                                                                                      
    {                                                                                                                                                                                                                                 
        let foo = |x: u32| {print!("{}", x)};                                                                                                                                                                                         
        let b = &Box::new(foo);                                                                                                                                                                                                       
        a = FuncWrapper::new(b);                                                                                                                                                                                                     
    }                                                                                                                                                                                                                                 
                                                                                                                                                                                                                                      
    let _c = (a.func)(42);                                                                                                                                                                                                            
}                                                                                                                                                                                                                                          

That is, I want for the return value of new to be an "owned" value.

Now I recently learned that all closures in Rust (being Trais) must have a lifetime associated with them (and it will default to 'static if not specified.) But I suppose its seems that any lifetime for the closure in the FuncWrapper of the return value of new will be wrong.

I don't want the lifetime to be that of the incoming reference, because this will be too short. E.g. the following will not work because it the lifetime of the return value is that of the parameter f_box_ref which does not live long enough in the case I provide above

struct FuncWrapper<'b, DomainType> {                                                                                                                                                                                                  
    func: Box<dyn Fn(DomainType) + 'b>                                                                                                                                                                                                
}                                                                                                                                                                                                                                     
                                                                                                                                                                                                                                      
impl<'b, DomainType> FuncWrapper<'b, DomainType> {                                                                                                                                                                                    
    fn new<F: Fn(DomainType) + 'static>(f_box_ref: &'b Box<F>) -> FuncWrapper<'b, DomainType> {                                                                                                                                       
        let new_f = move |a: DomainType| {                                                                                                                                                                                            
            // ..add some boilerplate then                                                                                                                                                                                            
            (**f_box_ref)(a)                                                                                                                                                                                                          
        };                                                                                                                                                                                                                            
        let b = Box::new(new_f);                                                                                                                                                                                                      
        FuncWrapper {func: b}                                                                                                                                                                                                         
    }                                                                                                                                                                                                                                 
}    

results in

error[E0716]: temporary value dropped while borrowed
  --> src/main.rs:22:18
   |
22 |         let b = &Box::new(foo);
   |                  ^^^^^^^^^^^^^ creates a temporary which is freed while still in use
23 |         a = FuncWrapper::new(b);
24 |     }
   |     - temporary value is freed at the end of this statement
...
27 | }
   | - borrow might be used here, when `a` is dropped and runs the destructor for type `FuncWrapper<'_, u32>`
   |
   = note: consider using a `let` binding to create a longer lived value

Changing the lifetime of the return parameter to 'static also seems wrong, since my goal is to create a new closure inside the new function`, (so how could it be static?) but regardless this:

struct FuncWrapper<DomainType> {                                                                                                                                                                                                      
    func: Box<dyn Fn(DomainType) + 'static>                                                                                                                                                                                           
}                                                                                                                                                                                                                                     
                                                                                                                                                                                                                                      
impl<DomainType> FuncWrapper<DomainType> {                                                                                                                                                                                            
    fn new<'b, F: Fn(DomainType) + 'static>(f_box_ref: &'b Box<F>) -> FuncWrapper<DomainType> {                                                                                                                                       
        let new_f = move |a: DomainType| {                                                                                                                                                                                            
            // ..add some boilerplate then                                                                                                                                                                                            
            (**f_box_ref)(a)                                                                                                                                                                                                          
        };                                                                                                                                                                                                                            
        let b = Box::new(new_f);                                                                                                                                                                                                      
        FuncWrapper {func: b}                                                                                                                                                                                                         
    }                                                                                                                                                                                                                                 
}                                                                                                                                                                                                                                     

errors with

error[E0759]: `f_box_ref` has lifetime `'b` but it needs to satisfy a `'static` lifetime requirement
  --> src/main.rs:7:21
   |
6  |       fn new<'b, F: Fn(DomainType) + 'static>(f_box_ref: &'b Box<F>) -> FuncWrapper<DomainType> {
   |                                                          ---------- this data with lifetime `'b`...
7  |           let new_f = move |a: DomainType| {
   |  _____________________^
8  | |             // ..add some boilerplate then
9  | |             (**f_box_ref)(a)
10 | |         };
   | |_________^ ...is used here...
11 |           let b = Box::new(new_f);
12 |           FuncWrapper {func: b}
   |                              - ...and is required to live as long as `'static` here

The error surprised me since I thought the job of move was to capture by value. At first I thought that I need to dereference the f_box_ref first? But that

impl<DomainType> FuncWrapper<DomainType> {                                                                                                                                                                                            
    fn new<'b, F: Fn(DomainType) + 'static>(f_box_ref: &'b Box<F>) -> FuncWrapper<DomainType> {                                                                                                                                       
        let f_box: Box<F> =  *f_box_ref;                                                                                                                                                                                              
        let new_f = move |a: DomainType| {                                                                                                                                                                                            
            // ..add some boilerplate then                                                                                                                                                                                            
            (*f_box)(a)                                                                                                                                                                                                               
        };                                                                                                                                                                                                                            
        let b = Box::new(new_f);                                                                                                                                                                                                      
        FuncWrapper {func: b}                                                                                                                                                                                                         
    }                                                                                                                                                                                                                                 
}                                                                                                                                                                                                                                     
           

results in

error[E0507]: cannot move out of `*f_box_ref` which is behind a shared reference
 --> src/main.rs:7:30
  |
7 |         let f_box: Box<F> =  *f_box_ref;
  |                              ^^^^^^^^^^
  |                              |
  |                              move occurs because `*f_box_ref` has type `Box<F>`, which does not implement the `Copy` trait
  |                              help: consider borrowing here: `&*f_box_ref`

Alas, following the hint to "help: consider borrowing here: &*f_box_ref" doesn't help either

impl<DomainType> FuncWrapper<DomainType> {                                                                                                                                                                                            
    fn new<'b, F: Fn(DomainType) + 'static>(f_box_ref: &'b Box<F>) -> FuncWrapper<DomainType> {                                                                                                                                       
        let f_box: &Box<F> =  &*f_box_ref;                                                                                                                                                                                            
        let new_f = move |a: DomainType| {                                                                                                                                                                                            
            // ..add some boilerplate then                                                                                                                                                                                            
            (*f_box)(a)                                                                                                                                                                                                               
        };                                                                                                                                                                                                                            
        let b = Box::new(new_f);                                                                                                                                                                                                      
        FuncWrapper {func: b}                                                                                                                                                                                                         
    }                                                                                                                                                                                                                                 
}

errors with

error[E0759]: `f_box_ref` has lifetime `'b` but it needs to satisfy a `'static` lifetime requirement
  --> src/main.rs:7:31
   |
6  |     fn new<'b, F: Fn(DomainType) + 'static>(f_box_ref: &'b Box<F>) -> FuncWrapper<DomainType> {
   |                                                        ---------- this data with lifetime `'b`...
7  |         let f_box: &Box<F> =  &*f_box_ref;
   |                               ^^^^^^^^^^^ ...is used here...
...
13 |         FuncWrapper {func: b}
   |                            - ...and is required to live as long as `'static` here

so obviously we're still not returning anything like a new owned closure, and any choice for the lifetime of the returned closure seems arbitrary and wrong.

What I'm trying to do here seems like it should be common. What am I missing?

2

There are 2 best solutions below

2
On

It's fairly straight forward, to get an owned type from a reference you have 3 options that come to mind immediately: Copy, Clone or ToOwned. ToOwned is a little more advanced since it can change the type as well and since Copy requires Cloned I'll focus on the later.

To get an owned Fn from a &Box<Fn> you can just call clone():

impl<DomainType> FuncWrapper<DomainType> {
    fn new<F: Fn(DomainType) + Clone + 'static>(f_box_ref: &Box<F>) -> FuncWrapper<DomainType> {
        let f_box = f_box_ref.clone();
        let new_f = move |a: DomainType| {
            // ..add some boilerplate then
            (f_box)(a)
        };
        let b = Box::new(new_f);
        FuncWrapper { func: b }
    }
}
0
On

Since it is not FnMut, just Fn maybe you can use an Rc instead of Box? You can always clone an Rc

use std::rc::Rc;

impl<DomainType> FuncWrapper<DomainType> {
    fn new<F: Fn(DomainType) + 'static>(f_rc: Rc<F>) -> FuncWrapper<DomainType> {
        let new_f = move |a: DomainType| {
            // ..add some boilerplate then
            (f_rc)(a)
        };
        let b = Box::new(new_f);
        FuncWrapper { func: b }
    }
}