Closures causes self to never be dropped

Hi, I'm having a problem with a function from Paho MQTT Rust library which is defined minimally as followed:

// Paho code
impl AsyncClient {
    pub fn set_connection_lost_callback(&self, cb: impl FnMut(&Self) + Send + 'static) {
        /* Stores the callback and when the client loses connection, it calls it. */
    }
}

I'm calling it in the constructor of my struct:

pub struct MyClient {
    client: paho::AsyncClient,
}

// Dummy impl
impl Drop for MyClient {
    fn drop(&mut self) {
        println!("Never prints");
    }
}

impl MyClient {
    pub fn new() -> Self {
        let client: paho::AsyncClient = ...;
        let (conn_lost_tx, conn_lost_rx) = async_channel::bounded(1);
        client.set_connection_lost_callback(move |_| {
            conn_lost_tx.send_blocking(());
        });
        glib::spawn_future_local(async move {
            loop {
                let Some(()) = conn_lost_rx.recv().await else {
                    return;
                };
                println!("Connection lost");
            }
        });
        Self {
            client
        }
    }
}

When client returned by new is dropped, it doesn't call dummy trait Drop. I tried closing the tx channel, passing weak channel to the callback, none of that works. It only works when the closure captures nothing:

client.set_connection_lost_callback(|_| {}); // Captures nothing

I'm not too much experienced in rust, but I think it has something to do with the closure being 'static and the capture itself capturing the stack memory from new function.

Thanks.

There isn’t enough information here to solve the problem, but I can tell you some things that it is not.

  1. It has nothing to do with 'static — lifetimes never affect when values are dropped.

  2. It must have to do with how MyClient::new() is being called, not the code inside it, because all of the code with channels and callbacks does not involve MyClient at all — only the last step Self { client } creates a MyClient. This value is returned to the caller, so it is up to the caller when the MyClient is dropped and executes its Drop::drop() code.

But the code that calls MyClient::new() doesn't change, it calls drop succesfully when the callback captures nothing, but when it captures the tx channel, it doesn't drop.

You are right, no enough information, but solved it. glib::spawn_future_local captured a weak clone of MyClient, and I was upgrading it before calling rx.recv().await. So when the caller of MyClient::new() was dropping it, there was still a reference inside the future.

Moved the order of calls and now it drops correctly :slight_smile:

Thanks.