3

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);
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
LunarEclipse
  • 661
  • 1
  • 4
  • 14
  • Is `MutexGuard` obtained from a `std::sync::Mutex` here? – sebpuetz Dec 20 '21 at 12:55
  • 2
    Re: "I have to put a lifetime annotation in Env struct signature, which means the environment can't outlive the `w_txn`" - you don't need lifetimes to make a struct not outlive its contents; that's impossible. I think you misunderstand the `'a` parameter. What code are you trying to write that doesn't compile? Can you create a [mre]? – trent Dec 20 '21 at 13:00
  • I just add a minimal reproducible example, the compiler says "cannot infer an appropriate lifetime due to conflicting requirements". – LunarEclipse Dec 20 '21 at 13:13
  • 1
    See also [Why can't I store a value and a reference to that value in the same struct?](https://stackoverflow.com/q/32300132/155423) – Shepmaster Dec 20 '21 at 16:24

3 Answers3

2

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.

Timmmm
  • 88,195
  • 71
  • 364
  • 509
  • 1
    [Here is a playground that works](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=ed49a469eb3551cc282f5d40af13d6fb) by adding mutex around `Env::w_txn`, but I agree with you that this is probably a bad design. – Chayim Friedman Dec 20 '21 at 22:25
  • Yeah I know, but I can't put all things to do in one function, so I have to hold the MutexGuard, to hold the MutexGuard, I have to use a lifetime annotation. I can't think of a better idea. – LunarEclipse Dec 21 '21 at 02:14
  • To assign the write mutex reference in Env, I choose to convert env into a raw pointer to do that, though it breaks rust rules. But I have mutex to make sure that only one thread is modifying the field. – LunarEclipse Dec 21 '21 at 02:18
0

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?

Alice Ryhl
  • 3,574
  • 1
  • 18
  • 37
0

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());
}
LunarEclipse
  • 661
  • 1
  • 4
  • 14
  • Note that this solution doesn't actually put a `MutexGuard` inside the environment, so the lifetime just becomes `'static`. – Alice Ryhl Dec 20 '21 at 16:33