Cannot pass arguments from struct to async closure in Rust

Hello, I am novice in Rust and I am struggling with following code:

pub struct TgSchedulerLive {
    client: Client,
    period: Interval,
    scheduler_handle: Option<JoinHandle<()>>,
    text: String,
    is_running: bool,
}

impl TgSchedulerLive {
    ...
  
    async fn run(&self) -> () {
        let mut dialog_iter = self.client.iter_dialogs();

        while let Ok(Some(dialog)) = dialog_iter.next().await {
            println!("{}", dialog.chat.name().unwrap_or_default());
        }
    }
}

impl TgScheduler for TgSchedulerLive {
    ...

    fn start(&mut self) -> () {
        let mut scheduler = AsyncScheduler::new();

        scheduler
            .every(self.period)
            .run(|| self.run());
        self.stop();
        ...

The signature of method run:

    /// Specify a task to run, and schedule its next run
    ///
    /// The function passed into this method should return a value implementing `Future<Output = ()>`.
    pub fn run<F, T>(&mut self, f: F) -> &mut Self
    where
        F: 'static + FnMut() -> T + Send,
        T: 'static + Future<Output = ()> + Send,
    {
        self.job = Some(Box::new(JobWrapper::new(f)));
        self.schedule.start_schedule();
        self
    }

I get the following error:

error[E0521]: borrowed data escapes outside of method
  --> src/scheduling.rs:51:9
   |
48 |       fn start(&mut self) -> () {
   |                ---------
   |                |
   |                `self` is a reference that is only valid in the method body
   |                let's call the lifetime of this reference `'1`
...
51 | /         scheduler
52 | |             .every(self.period)
53 | |             .run(|| self.run());
   | |                               ^
   | |                               |
   | |_______________________________`self` escapes the method body here
   |                                 argument requires that `'1` must outlive `'static`

I also tried this:

 fn start(&mut self) -> () {
        async fn run(client: &Client, text: &String) {
            let mut dialog_iter = client.iter_dialogs();

            while let Ok(Some(dialog)) = dialog_iter.next().await {
                println!("{}", dialog.chat.name().unwrap_or_default());
            }
        }

        let mut scheduler = AsyncScheduler::new();
        let client = self.client.clone();
        let text = self.text.clone();

        scheduler.every(self.period).run(move || run(&client, &text));

But i get the error:

error: captured variable cannot escape `FnMut` closure body
  --> src/scheduling.rs:53:50
   |
50 |         let client = self.client.clone();
   |             ------ variable defined here
...
53 |         scheduler.every(self.period).run(move || run(&client, &text));
   |                                                - ^^^^^------^^^^^^^^
   |                                                | |    |
   |                                                | |    variable captured here
   |                                                | returns an `async` block that contains a reference to a captured variable, which then escapes the closure body
   |                                                inferred to be a `FnMut` closure
   |
   = note: `FnMut` closures only have access to their captured variables while they are executing...
   = note: ...therefore, they cannot allow references to captured variables to escape

Could you please explain how to get this code work?

Did you define the TgScheduler trait yourself or is that from somewhere else?

If you did define it yourself, are you ever using it in such a way that .start is supposed to be callable more than once on the same object?

This code structure seems weird anyway. What is the semantics of .stop() and why is it called before the scheduler is used to do any work?

Anyways; to make the code just compile without changing semantics, you’ll need some way for run to have ownership (or shared ownership) of the things it needs to access to do its work. Which seems to be self.client. I don’t know what this Client type of yours is; if it’s a handle already, you could clone it; otherwise, you could wrap it in Arc perhaps.. e.g. roughly this could compile (after updating the construction of client as well):

pub struct TgSchedulerLive {
-   client: Client,
+   client: Arc<Client>,
    period: Interval,
    scheduler_handle: Option<JoinHandle<()>>,
    text: String,
    is_running: bool,
}

impl TgSchedulerLive {
    ...
  
-   async fn run(&self) -> () {
+   async fn run(client: Arc<Client>) -> () {
        let mut dialog_iter = client.iter_dialogs();

        while let Ok(Some(dialog)) = dialog_iter.next().await {
            println!("{}", dialog.chat.name().unwrap_or_default());
        }
    }
}

impl TgScheduler for TgSchedulerLive {
    ...

    fn start(&mut self) -> () {
        let mut scheduler = AsyncScheduler::new();

+       let client = Arc::clone(&self.client);
        scheduler
            .every(self.period)
-           .run(|| self.run());
+           .run(move || Self::run(client));
        self.stop();
        ...
1 Like

& references (like &self or &Client) are only a temporary permission to use the object they borrowed. In function arguments that's typically only for the duration of the function call, and not any longer. This applies recursively to all fields of that object.

The run function doesn't guarantee that it will stop before your outer call returns, so it can't use any of the temporary references. Such functions have a 'static bound, which in Rust means all temporary references are forbidden and can't be made to work. & isn't just a reference to an object, it's also a restriction how it can be used. Box and Arc are references that aren't restricted by temporary lifetimes.

Rust never makes anything live longer for you, and it's not possible extend lifetime of an existing temporary reference.