Search code examples
rustrust-tokiorust-futures

Rust needs static lifetime when waiting on the same future?


I am trying to get the OAuth token from the server, so I made a struct named Token. In order to keep only one request when the token is expired, I keep a reference for the requesting future, let other requests keep waiting for it returns. Playground:

use futures::lock::Mutex; // 0.3.18
use futures_util::{future::Shared, Future, FutureExt}; // 0.3.18
use std::{pin::Pin, sync::Arc};
use tokio::time::Duration; // 1.14.0

pub struct Token {
    pub token: String,
    pub requesting:
        Arc<Mutex<Option<Shared<Pin<Box<dyn Future<Output = Result<String, ()>> + Send>>>>>>,
}

impl Token {
    // Get token from server, I hope it does not run repeatly
    async fn get_access_token_from_server(&mut self) -> Result<String, ()> {
        tokio::time::sleep(Duration::from_secs(10)).await;
        self.token = "test".to_owned();
        Ok(self.token.clone())
    }

    //Shows error:`is required to live as long as `'static` here`, why?
    pub async fn access_token(&mut self) -> Result<String, ()> {
        /*
        if !self.token.is_expire() {
            return Ok(self.token.clone());
        }
        */

        let req_arc = self.requesting.clone();
        let req_mutex = req_arc.lock().await;

        let req = if req_mutex.is_some() {
            req_mutex.clone().map(|s| s.clone()).unwrap()
        } else {
            let req = self.get_access_token_from_server().boxed().shared();
            *req_mutex = Some(req.clone());
            req
        };

        req.await
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    /*
        Just example, in the real code, the token will keep in another struct with long life
    */
    let mut token = Token {
        token: String::from(""),
        requesting: Arc::new(Mutex::new(None)),
    };
    let token_string = token.access_token().await;
    println!("{:?}", token_string);
    Ok(())
}

I am confused about the lifetime errors:

error[E0759]: `self` has an anonymous lifetime `'_` but it needs to satisfy a `'static` lifetime requirement
  --> src/main.rs:21:31
   |
21 |     pub async fn access_token(&mut self) -> Result<String, ()> {
   |                               ^^^^^^^^^
   |                               |
   |                               this data with an anonymous lifetime `'_`...
   |                               ...is captured here...
...
35 |             *req_mutex = Some(req.clone());
   |                               ----------- ...and is required to live as long as `'static` here

I do not want to keep is as static; I hope its lifetime is the same as the struct.

  1. Is the normal way to solve the problem?
  2. How to correct the code?

Solution

  • Unfortunately, the compiler doesn't make it obvious where the constraint comes from. Storing a dyn Future<...> without any lifetime annotation defaults to 'static and you can't really change it to the lifetime of self because self-referential structs are problematic.

    Looking at your goal, you can use a thread-safe and async-capable OnceCell (from tokio for example) and skip all this complexity.

    use tokio::time::Duration;
    use tokio::sync::OnceCell;
    
    pub struct Token {
        pub token: OnceCell<String>,
    }
    
    impl Token {
        pub async fn access_token(&self) -> Result<&String, ()> {
            self.token.get_or_try_init(|| async {
                // simulate auth call
                tokio::time::sleep(Duration::from_secs(1)).await;
                Ok("test".to_owned())
            }).await
        }
    }
    

    See it working on the playground.

    I think it should behave as you expect. Multiple threads can await it but only one async task will execute and persist the value, and then any other awaits will simply fetch that value. And if the task returns an error, the next await will retry.