LunarEclipse
LunarEclipse

Reputation: 1011

How to decide lifetime annotations in circumstances like this?

I'm writing a transaction database. The Transaction struct holds a reference to a MutexGuard which needs a lifetime annotation, so I have to put a lifetime annotation on the transaction struct. The transaction also has a reference count to the environment struct:

struct Transaction<'a> {
    mutex_guard: MutexGuard<'a, i32>,
    env: Arc<Env> //I don't know which lifetime annotation to use here for Env.
}

I want the environment to have a weak reference to the write transaction:

struct Env<'a> {
    w_txn: Weak<Transaction<'a>>
}

I have to put a lifetime annotation on the Env struct, which means the environment can't outlive w_txn, but that's not what I want. I want the environment to always live longer than the transaction, that's why I use Weak.

So what should I do?

A minimal reproducible example:

use std::sync::{Arc, Mutex, MutexGuard, Weak};

#[derive(Debug)]
struct Env<'a> {
    w_txn: Option<Weak<Txn<'a>>>,
}

#[derive(Debug)]
struct Txn<'a> {
    env: Arc<Env<'a>>,
    mutex_guard: MutexGuard<'a, i32>,
}

impl Txn<'_> {
    fn new(env: Arc<Env>, mutex: &Mutex<i32>) -> Arc<Self> {
        Arc::new(Self {
            env: env.clone(),
            mutex_guard: mutex.lock().unwrap(),
        })
    }
}

fn main() {
    let mut env = Arc::new(Env { w_txn: None });
    let mut mutex = Mutex::new(0);
    let mut txn = Txn::new(env, &mutex);
    env.w_txn = Some(Arc::downgrade(&txn));
    println!("env: {:?}", env);
    println!("txn: {:?}", txn);
}

Upvotes: 3

Views: 763

Answers (3)

LunarEclipse
LunarEclipse

Reputation: 1011

After my exploration, I already found a solution.

use std::sync::{Mutex, MutexGuard, Arc, Weak};

#[derive(Debug)]
struct Env<'a> {
    mutex: Mutex<i32>,
    txn: Option<Weak<Txn<'a>>>
}

#[derive(Debug)]
struct Txn<'a> {
    mutex_guard: Option<MutexGuard<'a, i32>>,
    env: Arc<Env<'a>>
}

impl Env<'_> {
    fn new() -> Self {
        Self {
            mutex: Mutex::new(0),
            txn: None
        }
    }
}

impl<'a> Txn<'a> {
    fn new(env: &'a Arc<Env<'a>>) -> Self {
        Self {
            mutex_guard: Some(env.mutex.lock().unwrap()),
            env: env.clone()
        }
    }
}

fn main() {
    let mut env = Arc::new(Env::new());
    println!("create env");
    {
        let txn = Arc::new(Txn::new(&env));
        println!("create txn");
        //env.txn = Some(Arc::downgrade(&txn));
        //assert!(env.mutex.lock().is_err());
        println!("{:?}", txn);
        println!("{:?}", env);
        println!("{:?}", env.mutex.try_lock());
    }

    //assert!(env.mutex.lock().is_ok());
    println!("{:?}", env.mutex.try_lock());
}

Upvotes: 0

Timmmm
Timmmm

Reputation: 96832

This appears to fix the lifetime issues:

use std::sync::{Arc, Mutex, MutexGuard, Weak};

#[derive(Debug)]
struct Env<'a> {
    w_txn: Option<Weak<Txn<'a>>>,
}

#[derive(Debug)]
struct Txn<'a> {
    env: Arc<Env<'a>>,
    mutex_guard: MutexGuard<'a, i32>,
}

impl<'a> Txn<'a> {
    fn new(env: Arc<Env<'a>>, mutex: &'a Mutex<i32>) -> Arc<Self> {
        Arc::new(Self {
            env: env.clone(),
            mutex_guard: mutex.lock().unwrap(),
        })
    }
}

fn main() {
    let mut env = Arc::new(Env { w_txn: None });
    let mut mutex = Mutex::new(0);
    let mut txn = Txn::new(env, &mutex);
    env.w_txn = Some(Arc::downgrade(&txn));
    println!("env: {:?}", env);
    println!("txn: {:?}", txn);
}

However it will still not compile for various other reasons. The code looks like it needs a redesign to be honest. The stuff you are trying to do does not work well with Rust and in my experience that's a fairly big red flag that the design in general is not a good one.

Upvotes: 2

Alice Ryhl
Alice Ryhl

Reputation: 4239

Honestly, it's difficult to explain what the problem is exactly, but I am quite confident you don't want a lifetime on that Env struct. Putting a lifetime on Env will have the compiler ensure that the Env struct itself lives for a shorter duration than the lifetime (that's what it means for a lifetime to be annotated on a type), but the lifetime is the duration in which you locked the mutex, and I'm pretty sure you intended for the Env struct to be longer-lived than the mutex guard.

Putting the mutex guard into the Env struct is not going to be possible, at least not if your mutex has a lifetime annotated on it. For example, the Tokio mutex has an OwnedMutexGuard that doesn't have any lifetimes annotated on it because it is based on reference counting instead of lifetimes, but it is an async lock, and therefore not really appropriate for your example — unfortunately, I'm not aware of any crate that provides a non-async mutex with this feature.

Perhaps you could restructure your code so that the mutex guard would not be accessible from the Env struct?

Upvotes: 0

Related Questions