'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 |