Problems with lifetimes and async fn's

Viewed 200

I created some pseudo code below to demonstrate my problems. I want to execute a Fn with &mut self with async fn's. I tried it with and without lifetimes, both give different problems. This is what I want:

  • I have some type in which I want to call a method on, lets call it type A
  • In that method in type A, I want to call closure which takes a &mut self and the caller should be able to call async methods on it
  • I want to modify a property AFTER calling the closure (still in the same method).
  • I want to return something (something that the caller decides) The code looks something like this:

With lifetimes

use std::future::Future;

struct LineWriter {
    state: String
}

impl LineWriter {
    async fn start_writing_lines<'a, Return, F: Future<Output=Return>, M: FnOnce(&'a mut Self) -> F>(&'a mut self, f: M) -> Return {
        let r = f(self).await;

        self.state = "written".to_string();

        r
    }

    async fn write_newline(&mut self) {
        // do something
    }
}

#[tokio::main]
async fn main() {
    let mut lw = LineWriter { state: "".to_string() };

    lw.start_writing_lines(|l| l.write_newline()).await;
}

Error:

error[E0506]: cannot assign to `self.state` because it is borrowed
  --> src\main.rs:11:9
   |
8  |     async fn start_writing_lines<'a, Return, F: Future<Output=Return>, M: Fn(&'a mut Self) -> F>(&'a mut self, f: M) -> Return {
   |                                  -- lifetime `'a` defined here
9  |         let r = f(self).await;
   |                 -------
   |                 | |
   |                 | borrow of `self.state` occurs here
   |                 argument requires that `*self` is borrowed for `'a`
10 | 
11 |         self.state = "written".to_string();
   |         ^^^^^^^^^^ assignment to borrowed `self.state` occurs here

Why is self still borrowed? I can not understand that.

Without lifetimes:

use std::future::Future;

struct LineWriter {
    state: String
}

impl LineWriter {
    async fn start_writing_lines<Return, F: Future<Output=Return>, M: FnOnce(&mut Self) -> F>(&mut self, f: M) -> Return {
        let r = f(self).await;

        self.state = "written".to_string();

        r
    }

    async fn write_newline(&mut self) {
        // do something
    }
}

#[tokio::main]
async fn main() {
    let mut lw = LineWriter { state: "".to_string() };

    lw.start_writing_lines(|l| l.write_newline()).await;
}

Error:

error: lifetime may not live long enough
  --> src\main.rs:25:32
   |
25 |     lw.start_writing_lines(|l| l.write_newline()).await;
   |                             -- ^^^^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'2`
   |                             ||
   |                             |return type of closure `impl Future` contains a lifetime `'2`
   |                             has type `&'1 mut LineWriter`
1 Answers

This is a tricky one. I think the future created from the closure will borrow the reference for the duration of the start_writing_lines function, which includes the assignment to self.state. In a non-async function, this would be correct, but async lifetime rules are a bit different. I think it might not be possible to make this work. You might have to separately resolve the closure's future, allow the reference to be dropped, and then modify the state in another function, but I could be wrong on this.

This post explains a bit more async lifetimes and borrowing http://aturon.github.io/tech/2018/04/24/async-borrowing/

Related