'Lifetime of structure's object is shorter than lifetime of structure's member

I have some trouble to correctly understand lifetime concept in Rust. Especially in reference to structure's object itself and structure's member.
Following minimal example is showing an issue I struggle with:

use redis::{Connection, PubSub};

pub struct Db<'a> {
    conn: Connection,
    pubsub: Option<PubSub<'a>>
}

impl<'a> Db<'a> {
    fn open(address: &str) -> Self {
        let client = redis::Client::open(address).unwrap();
        let conn = client.get_connection().unwrap();
        Self {conn, pubsub: None}
    }

    fn subscribe(&'a mut self) {
        let pubsub = self.conn.as_pubsub();
        self.pubsub = Some(pubsub)
    }
}

fn main() {
    let mut db = Db::open("localhost");
    db.subscribe();
}

Compilation ends with following error:

error[E0597]: `db` does not live long enough
  --> src/main.rs:22:5
   |
22 |     db.subscribe();
   |     ^^^^^^^^^^^^^^ borrowed value does not live long enough
23 | }
   | -
   | |
   | `db` dropped here while still borrowed
   | borrow might be used here, when `db` is dropped and runs the destructor for type `Db<'_>`

For more information about this error, try `rustc --explain E0597`.

I need pubsub variable as structure's member and PubSub type has a field which is reference to redis::Connection. Therefore, lifetime must be defined for this structure explicitly.

As far as I understand, in subscribe function I'm defining that object itself must live at least for an a lifetime as well. However, compilation error informs me that when db is dropped it is still borrowed by pubsub reference. Isn't dropping db means dropping the reference as it is structure's member?

What should I do to fix this error? My goal is to still have pubsub as structure's member and subscribe function which utilizes self.conn. I have tried with another lifetime b defined for self, but didn't achieve anything and code still didn't compile.



Solution 1:[1]

This is a tricky problem with destructors, specifically the so-called "Drop Check". The error message refers to this as

borrow might be used here, when `db` is dropped and runs the destructor for type `Db<'_>`

You can read all the details about the Drop Check here, and you'll see that you essentially replicated the example given in the article.

The problem starts with the fact that PubSub has a destructor (an impl Drop for PubSub). Your Db type has an implicit destructor created for it, which calls the destructor on the PubSub- and the Connection-member when Db is destroyed. However, when the PubSub-destructor executes, it supposedly has access to things of lifetime of 'a (because it is a fn drop(&'a mut self), where 'a comes from PubSub<'a>, which comes from Db<'a>) and could potentially observe things of lifetime 'a that were already destroyed when the destructor for Db<'a> began its work. That is because the destructor of Db<'a> might have destroyed other members before it tried to destroy PubSub, and the compiler can't rule out that the PubSub-destructor could reach back into the partially destroyed Db.

The reasoning makes perfect sense: A PubSub is just a thin wrapper around &mut Connection. If this &mut Connection refers to the Connection in Db (which it does!), and the destructor for Db first destroyed Connection and then destroys PubSub, the destructor of PubSub accesses an already-destroyed Connection through the &mut Connection. The compiler chooses the safe route of rejecting the program.

The article mentioned above shows ways out of this. You could implement your own unsafe destructor for Db<'a>, which needs to "promise" to always destroy pubsub before it destroys conn. However, since PubSub is really only an thin layer around &mut Connection, its probably easiest to remove the pubsub member from Db<'a> and create it whenever required - its a zero-cost operation anyway.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1