USA LOVER
USA LOVER

Reputation: 155

lifetime may not live long enough when passing &mut inside async closure?

I want to write function transactional which handle logic related to transactions committing

fn get_client() -> Client {
    return Client{};
}

struct Client {}

impl Client {
    pub async fn commit(&mut self) -> Result<(), Error> {
        return Ok(());
    }

    pub async fn find_and_update(&mut self) -> Vec<u64> {
        return vec![];
    }
}


pub async fn transactional<F, Fut, R>(action: F) -> Result<R, Error>
where
F: Fn(&mut Client) -> Fut,
Fut: Future<Output = R>
{
    let mut client = get_client();
    loop {
        let action_result = action(&mut client).await;
        if let Err(err) = client.commit().await {
            continue;
        }
        return Ok(action_result);
    }
}

pub async fn make_request() -> Vec<u64> {
    return transactional(
        async move |session| session.find_and_update().await
    ).await.unwrap();
}

#[tokio::main]
async fn main() -> Result<(), io::Error>{
    let r = make_request().await;
    return Ok(())
}

but i get following error

   |         async move |session| session.find_and_update().await
   |         ^^^^^^^^^^^^--------
   |         |           |      |
   |         |           |      return type of closure `impl futures::Future<Output = Vec<u64>>` contains a lifetime `'2`
   |         |           has type `&'1 mut Client`
   |         returning this value requires that `'1` must outlive `'2`

is it possible to specify that &Client outlives Future and both lives less than loop iteration?

is it possible to fix this wihtout using pointers?

cargo --version
cargo 1.64.0-nightly (a5e08c470 2022-06-23)

Upvotes: 2

Views: 655

Answers (1)

Aitch
Aitch

Reputation: 1697

At first I rewrote your code to not use unstable async closures. Then you see, that borrowing Client in an async block |session| async move {..} is only possible for 'static and you don't have it. So you need to give it an owned value. In my case I pass ownership to the async block and return it in the result. Not sure if this is a good design, but it works.

use std::future::Future;
use tokio; // 1.19.2

fn get_client() -> Client {
    return Client{};
}

pub struct Client {}

impl Client {
    pub async fn commit(&mut self) -> Result<(), Error> {
        return Ok(());
    }

    pub async fn find_and_update(&mut self) -> Vec<u64> {
        return vec![];
    }
}

pub async fn transactional<F, Fut, R>(action: F) -> Result<R, Error>
where
F: Fn(Client) -> Fut,
Fut: Future<Output = (R, Client)>
{
    let mut client = get_client();
    loop {
        let (action_result, c) = action(client).await;
        client = c;

        if let Err(err) = client.commit().await {
            continue;
        }
        return Ok(action_result);
    }
}

pub async fn make_request() -> Vec<u64> {
    return transactional(|mut session| async move { // move `client` into async, not borrow
        let r = session.find_and_update().await;
        (r, session)
    }).await.unwrap();
}

#[tokio::main]
async fn main() -> Result<(), std::io::Error>{
    let r = make_request().await;
    return Ok(())
}

#[derive(Debug)]
pub struct Error;

Upvotes: 1

Related Questions