error[E0597]: `_` does not live long enough

Hello, I've been playing around with actix-web and wanted to properly structure my code. But, I ran into an error that I've been unable to find a solution for, yet.

I wanted have an Application structure that will be called from the route handlers. It looks like this:

// Queue is a trait
pub struct Application<T: Queue> {
    queue: T,
}

impl<T: Queue> Application<T> {
    pub fn new(queue: T) -> Self {
        Application {
            queue: queue,
        }
    }

    pub async fn get_home(&self) -> String {
        String::from("Hello from Application!")
    }
}

And, to use the, the main actix-web application configuration is:

#[actix_web::main]
async fn main() -> Result<(), Error> {
    let config = ServerConfig::from_env()?;

    // SqsQueue implements the Queue trait
    let queue = SqsQueue::from_env().await?;
    let application = Application::new(queue);

    HttpServer::new(|| {
        App::new()
            .service(web::resource("/").to(|| async {
                application.get_home().await
            }))
    })
    .bind(config.get_address())?
    .run()
    .await?;

    Ok(())
}

On building, I'm running into the following error:

error[E0597]: `application` does not live long enough
  --> service/src/main.rs:20:17
   |
17 |       HttpServer::new(|| {
   |                       -- value captured here
18 |           App::new()
19 |               .service(web::resource("/").to(|| async {
   |  ______________________-
20 | |                 application.get_home().await
   | |                 ^^^^^^^^^^^ borrowed value does not live long enough
21 | |             }))
   | |______________- argument requires that `application` is borrowed for `'static`
...
32 |   }
   |   - `application` dropped here while still borrowed

Borrowing, ownership, and lifetime are still topics that I'm struggling with.

Why does this error occur? Is it because of the multiple closures? And how should it be resolved?

I would really appreciate your help! Thanks!

You will probably need to put the Application in an Arc and clone the Arc for each request handler.

2 Likes

Hey, @alice ! Thanks for the response!

Using Arc results in a similar error:

#[actix_web::main]
async fn main() -> Result<(), Error> {
    let config = ServerConfig::from_env()?;
    let queue = SqsQueue::from_env().await?;
    let application = Arc::new(Application::new(queue));

    HttpServer::new(|| {
        App::new()
            .service(web::resource("/").to(|| async {
                let app_clone = Arc::clone(&application);
                app_clone.get_home().await
            }))
    })
    .bind(config.get_address())?
    .run()
    .await?;

    Ok(())
}
error[E0597]: `application` does not live long enough
  --> service/src/main.rs:20:45
   |
17 |       HttpServer::new(|| {
   |                       -- value captured here
18 |           App::new()
19 |               .service(web::resource("/").to(|| async {
   |  ______________________-
20 | |                 let app_clone = Arc::clone(&application);
   | |                                             ^^^^^^^^^^^ borrowed value does not live long enough
21 | |                 app_clone.get_home().await
22 | |             }))
   | |______________- argument requires that `application` is borrowed for `'static`
...
33 |   }
   |   - `application` dropped here while still borrowed

If I also use Arc::clone in the outermost closure and resolve the moves, as follows:

#[actix_web::main]
async fn main() -> Result<(), Error> {
    let config = ServerConfig::from_env()?;
    let queue = SqsQueue::from_env().await?;
    let application = Arc::new(Application::new(queue));

    HttpServer::new(move || {
        let application_clone = Arc::clone(&application);
        App::new()
            .service(web::resource("/").to(move || async {
                let app_clone = Arc::clone(&application_clone);
                app_clone.get_home().await
            }))
    })
    .bind(config.get_address())?
    .run()
    .await?;

    Ok(())
}

It results in this error:

error: lifetime may not live long enough  --> service/src/main.rs:20:52
   |
20 |               .service(web::resource("/").to(move || async {
   |  ____________________________________________-------_^
   | |                                            |     |
   | |                                            |     return type of closure `impl std::future::Future` contains a lifetime `'2`
   | |                                            lifetime `'1` represents this closure's body
21 | |                 let app_clone = Arc::clone(&application_clone);22 | |                 app_clone.get_home().await
23 | |             }))
   | |_____________^ returning this value requires that `'1` must outlive `'2`
   |
   = note: closure implements `Fn`, so references to captured variables can't escape the closure

Your clones are in the wrong place.

#[actix_web::main]
async fn main() -> Result<(), Error> {
    let config = ServerConfig::from_env()?;
    let queue = SqsQueue::from_env().await?;
    let application = Arc::new(Application::new(queue));

    HttpServer::new(move || {
        let application = Arc::clone(&application);
        App::new()
            .service(web::resource("/").to(move || {
                let application = Arc::clone(&application);
                async move {
                    application.get_home().await
                }
            }))
    })
    .bind(config.get_address())?
    .run()
    .await?;

    Ok(())
}
3 Likes

It builds! Thank you so much, @alice !

If you don't mind, can you please point me to some resources or let me know what I should search for to understand why it was not working earlier, and why this solution worked?

1 Like

The fundamental concept is that all of the closures and async blocks must have full ownership of their contents, but closures that can be called more than once cannot give away ownership without first making a clone. The closure in web::resource("/").to(...) is such a closure that can be called more than once, so it needs to clone the Arc.

2 Likes

I have a follow-up question. The solution works and it's great, but I found that as I wanted to add more route handlers, I had to create multiple clones for each handler.

#[actix_web::main]
async fn main() -> Result<(), Error> {
    let config = ServerConfig::from_env()?;
    let queue = SqsQueue::from_env().await?;

    let application = Arc::new(Application::new(queue));

    HttpServer::new(move || {
        // Have to create a clone for each handler :(
        let get_application_clone = Arc::clone(&application);
        let post_application_clone = Arc::clone(&application);
        App::new()
            .route(
                "/",
                web::get().to(move || {
                    let app_clone = Arc::clone(&get_application_clone);
                    async move { app_clone.get_home().await }
                }),
            )
            .route(
                "/send",
                web::post().to(move |body: web::Json<Value>| {
                    let app_clone = Arc::clone(&post_application_clone);
                    let body = body.into_inner();
                    async move {
                        app_clone.send(body).await;
                        HttpResponse::Ok()
                    }
                }),
            )
    })
    .bind(config.get_address())?
    .run()
    .await?;

    Ok(())
}

It doesn't feel like a very clean approach, quite opposite to what I had in mind when I introduced using Application to better separate layers. With a lot of route handlers, I don't want to imagine how it would look.

Is this the only way to solve this issue? Is there a workaround that would be better?

route function should not bind to the Application, you should refactor it.

build a Sqsueue object and pass it by

let sqs = SqsQueue::from_env().await? 
App::new().data(sqs).route("/", web::get().to(get_home))

and write the async function to operation the queue.

async fn get_home(  pool: web::Data<SqsQueue>) {
   .... 
} 

and I recommend you to ask question about actix-web at actix/actix-web - Gitter

2 Likes

When you encounter this problem in other contexts, you can use a macro, but it sounds like actix-web has a better solution.

1 Like

Thanks, @somewheve ! That certainly seems like a cleaner approach.

Also, thanks for pointing me to the actix-web Gitter, it's very helpful!

https://github.com/actix/examples/blob/516faf22dbd0d3a8cb0aa85ca3acd331cb407882/database_interactions/redis/src/main.rs#L82

there is a Redis example in here, for reference only

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.