Understanding a "may not live long enough" error inside move closure

I've been working on a web socket server and I am running into an odd error that I can't wrap my head around. I am using Warp web server and I have a ws handler like so:

    pub async fn ws_handler(ws: warp::ws::Ws, chat_id: String, chat_manager: impl ChatManager, req_executor: Arc<impl RequestExecutor<String>>) -> Result<impl warp::Reply, Rejection> {
        let chat = chat_manager.get_chat(&chat_id).await.map_err(|_| warp::reject::not_found())?;

        Ok(ws.on_upgrade(move |socket| {
            handle_websocket(socket, chat, req_executor)
        }))

    }

The error I am getting is:

error[E0310]: the parameter type `impl RequestExecutor<String>` may not live long enough
  --> src/public_api/chat_websocket.rs:81:15
   |
71 |     pub async fn ws_handler(ws: warp::ws::Ws, chat_id: String, chat_manager: impl ChatManager, req_executor: Arc<impl RequestExecutor<String>>) -> Result<impl warp::Reply, Rejection> {
   |                                                                                                                               ----------------------------------- help: consider adding an explicit lifetime bound...: `impl RequestExecutor<String> + 'static`
...
81 |         Ok(ws.on_upgrade(move |socket| {
   |               ^^^^^^^^^^ ...so that the type `[closure@src/public_api/chat_websocket.rs:81:26: 83:10 chat:chat::Chat, executor:std::sync::Arc<impl RequestExecutor<String>>]` will meet its required lifetime bounds

I am particularly confused because I can pass the chat struct just fine, but not the req_executor. My understanding is that by using the move keyword, it should be taking ownership of the req_executor without issues. I have also tried calling clone() on the req_executor and calling clone right after the creation of the chat to make a cloned_req_executor. Neither of those make the error go away either

Apparently, that req_executor needs to have 'static as a lifetime. Can you please show what the on_update function looks like ?

on_upgrade is a function from the warp library:

/// Finish the upgrade, passing a function to handle the `WebSocket`.
    ///
    /// The passed function must return a `Future`.
    pub fn on_upgrade<F, U>(self, func: F) -> impl Reply
    where
        F: FnOnce(WebSocket) -> U + Send + 'static,
        U: Future<Output = ()> + Send + 'static,
    {
        WsReply {
            ws: self,
            on_upgrade: func,
        }
    }

Well as you can see, it's parameters should have a 'static lifetime, while you are passing a parameter that does not have a 'static lifetime.

The compiler suggests you replace
impl RequestExecutor<String>
by
impl RequestExecutor<String> + 'static

I can go back and make everything + 'static, I'm really trying to understand what + 'static means in this context with impl Trait. Does this mean it will only take owned references and not borrowed references?

No, if something has a 'a lifetime, it will be created during 'a and will be freed when 'a ends. This prevents something being used after it was freed. 'static is a special lifetime that lasts for the whole program

Imagine T = &'borrow String. You can own a(n instance of type) T, and yet the instance is unusable after the 'borrow lifetime ends.

Saying that a type is 'static (e.g., T : 'static or impl ... + 'static + ...) means that your type does not contain any such short-lived reference / borrows, so that one can own it indefinitely.

See also the following posts:

2 Likes

Example here https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=961b73a53e52dfa6bff7765fb8fe2e61

You can see how and why lifetimes are useful : they prevent you from having something destroyed / freed still being referenced somewhere.