"lifetime may not live long enough" when using RwLock

I just run into a very strange problem when porting one library over to Rust.

Here is MRE after narrowing this one down:

use std::sync::{Arc, RwLock};

struct Database<'a>{
    foo: &'a i32
}

struct Watcher<'a> { database: Arc<RwLock<Database<'a>>> }

impl<'a> Watcher<'a> {
    fn new<'b>(database: Arc<RwLock<Database<'b>>>) -> Self
        where 'b: 'a
    {
        Self { database } 
    }
}

fn main() {
    
}

I need Database to outlive Watcher and it looks by the meaning of code that it indeed should. Strangely enough compiler proposes me to induce reverse lifetime dependency (that Watcher lives at least as long as Database). As for reason, it gives me message "requirement occurs because of the type `RwLock<Database<'_>>`, which makes the generic argument `Database<'_>` invariant" which I cannot understand in this context.

How can I fix the following code so the Database will correctly outlive Watcher?

In the live app the Watcher, living shorter than Database, will notify Database about changes that occurred in other threads.

You should not be putting temporary references in structs. The struct …<'a> syntax is a special case for temporary views of data stored elsewhere, and it's a design mistake to use it for structs that are supposed to hold some data.

&'a is not for storing data by reference. It's for not storing the data at all, and having it tied to some local variable or content of some other struct stored elsewhere.

It's not possible to extend an existing temporary lifetime, even if you put the struct in a longer-lived container.

So the best solution is to remove all the lifetimes. If you want to store something "by reference", the correct type for it is Box (or Arc).

struct Database {
    foo: i32,
}

struct Watcher { database: Arc<RwLock<Database>> }

impl Watcher {
    fn new<'b>(database: Arc<RwLock<Database>>) -> Self
    {
        Self { database } 
    }
}

but if you wanted to hold a temporary scope-bound reference in the struct, then this works:

impl<'a> Watcher<'a> {
    fn new(database: Arc<RwLock<Database<'a>>>) -> Self
    {
        Self { database } 
    }
}

What happens here with 'b: 'a is that the lifetime in RwLock ends up being invariant, i.e. it cannot be shortened. This is because RwLock is mutable (equivalent of &mut), and shortening of a mutable lifetime has edge cases that can lead to use-after-free. Shared & is flexible, but exclusive lifetimes are not. There are cases where &mut appears to be shortened, but it's the compiler "reborrowing" it and implicitly creating a new inflexible lifetime, while forbidding access to the longer original. Simple assignments don't get the reborrowing.

5 Likes