Method of a struct shared as a callback with other struct and gets called from spawned thread

Hi. Can't figure out how to implement this the cleanest, leanest and most idiomatic way. I simplified the code so it looks like trying to use fn() pointers, but I tried a lot with Arc<Mutex<dyn FnMut() + Send + Sync + 'static>> and similar stuff.

struct CallbackOwner {
    is_done: bool,
    caller: CallbackCaller,
}

impl CallbackOwner {
    fn new() -> Self {
        Self {
            is_done: false,
            caller: CallbackCaller::new(),
        }
    }

    pub fn run(&mut self) {
        self.caller.run(|| self.owner_mutating_function()));

        loop {
            if self.is_done {
                break;
            }
        }
    }

    pub fn owner_mutating_function(&mut self) -> {
        self.is_done = true;
    }
}

struct CallbackCaller {.....}

impl CallbackCaller {
    pub fn run(&mut self, callback: fn()) {
        std::thread::spawn(move || {
            .......
            callback();
        }
    }
}

I get the error:

        self.caller.run(|| self.callback()));
expected fn pointer, found closure
closures can only be coerced to `fn` types if they do not capture any variables

Here's a playground with some syntax corrections and simplification.

error[E0500]: closure requires unique access to `*self` but it is already borrowed
  --> src/lib.rs:15:24
   |
15 |         self.other.run(|| self.callback());
   |         ---------- --- ^^ ---- second borrow occurs due to use of `*self` in closure
   |         |          |   |
   |         |          |   closure construction occurs here
   |         |          first borrow later used by call
   |         borrow occurs here
    pub fn run(&mut self) {
        self.other.run(|| self.callback());
    }

    pub fn callback(&mut self) {
        self.is_done = true;
    }

What the error is pointing out is that Self::callback requires a &mut self -- an exclusive borrow of all of Self. So the closure requires (captures) an exclusive borrow of all of Self. There's no way to operate on self.other or any other field so long as the closure exists.

Temporarily removing self.other may be an option:

    pub fn run(&mut self) {
        let mut other = std::mem::replace(&mut self.other, CallbackCaller::new());
        other.run(|| self.callback());
        self.other = other;
    }

But it really depends on the bigger picture. (Also be aware that a panic could cause an unwind, bypassing the restorative self.other = other unless you take steps to catch the unwind.)

Factoring or view structs may also be an option as explored here.

1 Like

gets called from spawned thread

I deleted this part in my playground, but another point is that spawn requires a 'static closure, so no closure that captures the temporary &mut self reference is going to work. You maybe be able to use scoped threads instead.

    pub fn run<F: Send + FnOnce()>(&mut self, callback: F) {
        std::thread::scope(|scope| {
            scope.spawn(|| callback());
        });
    }

However note that

All threads spawned within the scope that haven’t been manually joined will be automatically joined before this function returns.

1 Like

Thanks for the std::thread::scope idea. After digging for it in Rust docs, this part is especially relevant to understand what's going on:

From scope in std::thread - Rust

Scoped threads involve two lifetimes: 'scope and 'env.

The 'scope lifetime represents the lifetime of the scope itself. That is: the time during which new scoped threads may be spawned, and also the time during which they might still be running. Once this lifetime ends, all scoped threads are joined. This lifetime starts within the scope function, before f (the argument to scope) starts. It ends after f returns and all scoped threads have been joined, but before scope returns.

The 'env lifetime represents the lifetime of whatever is borrowed by the scoped threads. This lifetime must outlast the call to scope, and thus cannot be smaller than 'scope. It can be as small as the call to scope, meaning that anything that outlives this call, such as local variables defined right before the scope, can be borrowed by the scoped threads.

The 'env: 'scope bound is part of the definition of the Scope type.

After some tinkering with this, I got the setup working in the playground:

I had to get rid of self.owner_mutating_function() though and directly mutate struct state in the closure, because the method run in which the closure is defined itself borrows the CallbackOwner instance and the closure can't receive two mutable borrows to self.

Thanks a lot! :100: