How problematic is it to use this closure pattern?

Many are saying that callbacks are very hard to use and unpleasant/ to be avoided.

Is this setup something that is already too much to ask for rust?
Sorry if the code is too unspecific. I hope that this is enough to hint at some idea.


some_crate::sys::init(|api| {
        
        let some_struct:...
        let some_hashmap:...
        
        |update| {  // gets called on websocket data
            some_struct.update(data);
        }

    }, (config));

It's hard to say with just pseudo code. We can't see what your lifetime requirements are going to be, exactly. If I had to guess, it looks like you want to be able to create this closure that references some_struct and pass the closure to some other thread, then return from init.

This pattern is hard to work with because it requires some_struct to outlive the other thread that the closure is sent to. It also means you cannot move some_struct after the closure takes a reference [1]. Arc<T> is usually the tool for working around the ownership problem, but Arc itself only provides read-only access to T. So, you inevitably end up with Arc<Mutex<T>> to also provide interior mutability.

If, on the other hand, you are not passing the closure to another thread, the situation may well be different enough that this advice is useless. That's why it is hard to say just by looking at pseudo code.


  1. E.g., you can't just return from init because that would drop some_struct. And you can't return the struct by value because that would move it out of the stack frame and invalidate the reference held by the closure. ↩︎

I'm already running into this problem:

init(|| {

        let mut x:i32 = 0;
        
        |n:i32| {

            x += 1;

            println!("::{}, {}", n, x);
        }
    
    }, ());

error[E0308]: mismatched types
  --> src\main.rs:48:9
   |
48 | /         |n:i32| {
49 | |
50 | |             x += 1;
51 | |
52 | |             println!("::{}, {}", n, x);
53 | |         }
   | |_________^ expected fn pointer, found closure
   |
   = note: expected fn pointer `fn(i32)`
                 found closure `[closure@src\main.rs:48:9: 48:16]`
note: closures can only be coerced to `fn` types if they do not capture any variables
  --> src\main.rs:50:13
   |
50 |             x += 1;
   |             ^ `x` captured here

For more information about this error, try `rustc --explain E0308`.

... Which is exactly what I described. You are borrowing a variable that is local to the init stack frame and then dropping the variable.

There's an old joke that goes something like:

A man tells his doctor, "it hurts when I do this..." and the doctor replies, "well, don't do that!"

So, just, like, don't do that.

2 Likes

You are probably writing fn(i32) somewhere where you’d either need to be generic over the closure type or require Box<dyn Fn…>-style types.

(In case you want to work with boxed trait objects, it could still be convenient to make the init function generic over the (closure) return type of the passed-in closure, and do the boxing behind the scenes, in order to have the thing be more convenient to call.)

The next problem with this code

would then be that you’re returning a closure referencing a local variable; but in this case, the solution would probably be a straightforward change to a move closure (move |update| { … }).


In case you’re interested in more concrete help with this error message you’ve posted above, feel free to share the code producing it, and we can suggest concrete changes / improvements :slight_smile:

2 Likes