一生都在为 lazy_static 的价值而奋斗 "borrowed value does not live long enough"

Lifetime struggles with "borrowed value does not live long enough" for lazy_static value

这里的 Rust 新手已经为如何让编译器识别 lazy_static 结构实例的生命周期是“静态的”而苦苦挣扎了一整天。我正在尝试做的一个最小示例如下:

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()
}

但我收到以下错误:

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

我不明白这一点,因为 RECEIVER_CONNECTIONS 是一个 lazy_static 变量,而且我认为我的代码没有在函数末尾使用 receiver_connections_instance

非常感谢和无限的业力,无论谁能帮助我理解我在这里做错了什么。 :)

TLDR 是相关参考不是 'static,因为它与互斥保护的生命周期相关。我将通过浏览代码的相关部分来解释这个问题。

您首先锁定 RECEIVER_CONNECTIONS 互斥锁,将守卫存储在 receiver_connections_instance:

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

然后你在守卫中得到一个对数据的可变引用,并将它存储在receiver_connection:

let receiver_connection = &mut receiver_connections_instance[receiver_connection_index];

然后您在 receiver_connection 上调用 as_pubsub() 方法并将结果存储在 receiver:

let receiver = receiver_connection.as_pubsub();

as_pubsub() 方法的签名如下:

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

如果我们取消生命周期可以写成

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

我们从生命周期可以看出,return类型PubSub捕获了输入生命周期。 (这是因为 PubSub 将可变引用存储在自身内部)。所以所有这些都意味着 receiver 的生命周期与互斥锁守卫的生命周期相关联。随后的代码尝试将 receiver 存储在静态 RECEIVERS 变量中,但这不起作用,因为 receiver 不能比互斥保护 receiver_connections_instance 长寿,后者在最后被丢弃功能。

问题是在您调用 as_pubsub() 时您的 Connection 不是 'static,您通过生命周期有限的互斥保护来访问它。一旦你放下警卫,连接就不再是你独有的,PubSub 也不是——这就是为什么 PubSub<'static> 是不允许的。 redis Rust API 似乎不允许 完全 你所追求的(至少没有不安全),因为 Connection::as_pubsub() 需要 &mut self,禁止您直接在全局存储的 Connection.

上调用 as_pubsub()

但是由于您的连接是全局的并且无论如何都不会被删除,您可以简单地不存储连接,而是“泄漏”它并且只存储 PubSub。这里 leak 在技术意义上是指创建一个分配的值,然后永远不会删除,就像一个全局变量,而不是表示错误的不受控制的内存泄漏。泄漏连接会给你 &'static mut Connection,你可以用它来创建一个 PubSub<'static>,你可以将它存储在一个全局变量中。例如,这样编译:

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)
}

几个切线注释:

  • redis Client不需要包裹在Mutex中因为get_connection()需要&self.
  • 您不需要对每个互斥锁进行模式匹配 - 仅当持有该锁的线程发生恐慌时,锁定才会失败。在那种情况下,您很可能只想传播恐慌,因此 unwrap() 是合适的。
  • 使用 0 作为特殊值不是惯用的 Rust,您可以使用 Option<u64>Result<u64> 来表示无法返回值。这允许函数使用 ? 运算符。

上面的代码应用了这些改进,从而显着减少了行数。