skip to Main Content

Rust newbie here that has been struggling for a full day on how to get the compiler to recognize that the lifetime of a lazy_static struct instance is ‘static. A minimal example of what I am trying to do is the following:

use redis::{Client, Connection, PubSub};
use std::sync::Mutex;

#[macro_use]
extern crate lazy_static;

lazy_static! {
    static ref REDIS_CLIENT: Mutex<Client> =
        Mutex::new(Client::open("redis://127.0.0.1/").unwrap());
    static ref RECEIVER_CONNECTIONS: Mutex<Vec<Connection>> = Mutex::new(vec![]);
    static ref RECEIVERS: Mutex<Vec<PubSub<'static>>> = Mutex::new(vec![]);
}

pub fn create_receiver() -> u64 {
    let client_instance = match REDIS_CLIENT.lock() {
        Ok(i) => i,
        Err(_) => return 0,
    };

    let connection: Connection = match client_instance.get_connection() {
        Ok(conn) => conn,
        Err(_) => return 0,
    };

    let mut receiver_connections_instance = match RECEIVER_CONNECTIONS.lock() {
        Ok(i) => i,
        Err(_) => return 0,
    };

    let receiver_connection_index = receiver_connections_instance.len();
    receiver_connections_instance.push(connection);

    let receiver_connection = &mut receiver_connections_instance[receiver_connection_index];
    let receiver = receiver_connection.as_pubsub();

    let mut receivers_instance = match RECEIVERS.lock() {
        Ok(i) => i,
        Err(_) => return 0,
    };

    receivers_instance.push(receiver);
    let receiver_handle = receivers_instance.len();

    receiver_handle.try_into().unwrap()
}

But I am getting the following error:

error[E0597]: `receiver_connections_instance` does not live long enough
  --> src/lib.rs:33:36
   |
33 |     let receiver_connection = &mut receiver_connections_instance[receiver_connection_index];
   |                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ borrowed value does not live long enough
34 |     let receiver = receiver_connection.as_pubsub();
   |                    ------------------------------- argument requires that `receiver_connections_instance` is borrowed for `'static`
...
45 | }
   | - `receiver_connections_instance` dropped here while still borrowed

I don’t understand this because RECEIVER_CONNECTIONS is a lazy_static variable and I don’t think my code uses the receiver_connections_instance past the end of the function.

Many thanks and infinite karma to whoever can help me understand what I’m doing wrong here. 🙂

2

Answers


  1. The TLDR is that the relevant reference is not 'static because it’s tied to the lifetime of the mutex guard. I’ll explain the issue by walking through the relevant parts of the code.

    You start by locking the RECEIVER_CONNECTIONS mutex, storing the guard in receiver_connections_instance:

    let mut receiver_connections_instance = match RECEIVER_CONNECTIONS.lock() {
        Ok(i) => i,
        Err(_) => return 0,
    };
    

    Then you get a mutable reference to data inside the guard, and store it in receiver_connection:

    let receiver_connection = &mut receiver_connections_instance[receiver_connection_index];
    

    You then call the as_pubsub() method on receiver_connection and store the result in receiver:

    let receiver = receiver_connection.as_pubsub();
    

    The signature of that as_pubsub() method is the following:

    fn as_pubsub(&mut self) -> PubSub<'_>
    

    which if we un-elide the lifetimes can be written as

    fn as_pubsub<'a>(&'a mut self) -> PubSub<'a>
    

    We can see from the lifetimes that the return type PubSub captures the input lifetime. (This is because PubSub stores the mutable reference inside itself). So all of this means the lifetime of receiver is bound to the lifetime of the mutex guard. The code that follows then tries to store receiver in the static RECEIVERS variable, but that cannot work because receiver cannot outlive the mutex guard receiver_connections_instance, which is dropped at the end of the function.

    Login or Signup to reply.
  2. The problem is that your Connection isn’t 'static at the time you invoke as_pubsub(), you access it through a mutex guard with a limited lifetime. As soon as you drop the guard, the connection is no longer exclusively yours, and neither is the PubSub – which is why PubSub<'static> is not allowed. The redis Rust API doesn’t seem to allow exactly what you’re after (at least without unsafe), because Connection::as_pubsub() requires &mut self, prohibiting you from invoking as_pubsub() directly on a globally stored Connection.

    But since your connections are global and never removed anyway, you could simply not store the connection, but "leak" it instead and only store the PubSub. Here leak is meant in a technical sense of creating a value that is allocated and then never dropped, much like a global variable, not to an uncontrolled memory leak that would indicate a bug. Leaking the connection gives you &'static mut Connection which you can use to create a PubSub<'static>, which you can store in a global variable. For example, this compiles:

    lazy_static! {
        static ref REDIS_CLIENT: Client = Client::open("redis://127.0.0.1/").unwrap();
        static ref RECEIVERS: Mutex<Vec<PubSub<'static>>> = Default::default();
    }
    
    pub fn create_receiver() -> RedisResult<usize> {
        let connection = REDIS_CLIENT.get_connection()?;
        let connection = Box::leak(Box::new(connection)); // make it immortal
    
        let mut receivers = RECEIVERS.lock().unwrap();
        receivers.push(connection.as_pubsub());
        Ok(receivers.len() - 1)
    }
    

    Several tangential notes:

    • redis Client doesn’t need to be wrapped in Mutex because get_connection() takes &self.
    • you don’t need to pattern-match every mutex lock – locking can fail only if a thread that held the lock panicked. In that case you most likely want to just propagate the panic, so an unwrap() is appropriate.
    • using 0 as a special value is not idiomatic Rust, you can use Option<u64> or Result<u64> to signal that a value could not be returned. That allows the function to use the ? operator.

    The code above has these improvements applied, resulting in a significantly reduced line count.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search