Passing Mutex and MutexGuard in a struct
Asked Answered
L

1

6

I am trying to return a struct containing reference to a shared mutex:

struct Test<'a> {
    mutex: Arc<Mutex<()>>,
    guard: &'a MutexGuard<'a, ()>,
}

impl<'a> Test<'a> {
    pub fn new() -> Self {
        let mutex = Arc::new(Mutex::new(()));
        let guard = &mutex.lock().unwrap();
        Self {
            mutex,
            guard,
        }
    }
}

The lifetimes seem correct: the mutex exists at least during the lifetime of Test, so MutexGuard does not have a stalled reference to mutex. But Rust gives errors. How to explain to Rust that the lifetime of mutex field is enough long for guard to work well?

cannot return value referencing local variable `mutex`
returns a value referencing data owned by the current function

BTW, I am trying to create a "mutli-mutex" - a set of mutexes for keys (like as in HashMap), to block downloading a file whose name is in hashmap (because it is already downloading).

Lukewarm answered 21/1, 2022 at 19:42 Comment(6)
The MutexGuard in the struct shouldn't be a reference, it should be just MutexGuard<'a, ()>, otherwise you're returning a reference to a local variable which just can't work. But even after making this change, guard is pointing inside mutex, so you're effectively creating a self-referential data structure, and that won't compile. The only way for that to work is using unsafe.Astigmatic
@Astigmatic How to use unsafe in this situation?Lukewarm
crates.io/crates/lockpool solves my real problem.Lukewarm
Does lockpool then answer your question? You can take a look at how they use unsafe, but there are probably other approaches.Astigmatic
Does this answer your question? Why can't I store a value and a reference to that value in the same struct?Manicurist
This seems to be an xy problem, the OPs original need seems satisfied by the lockpool crate or it's successor lockable. I'm not sure what the purpose of the bounty is - are we trying to solve the OPs actual problem of creating a lockable hash, or the expressed problem of making a self-referential struct (which is anyway a duplicate)Cist
S
0

It is not possible, also what you wrote is most probably not what you meant to do.

The mutex guard has the goal to unlock the mutex when it's dropped. If you bind a lifetime of the guard to the mutex itself, you are saying that it will never be dropped, thus the mutex will always be locked.

Anyway, why do you need the reference to the guard itself? Usually you wouldn't care, you just expect it to keep the lock, as long as it's referenced.

Maybe you want to do this? One needs more context to see if this is exactly what you are trying to do :D

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

struct Test {
    mutex: Arc<Mutex<()>>,
}

impl Test {
    pub fn new() -> Self {
        let mutex = Arc::new(Mutex::new(()));
        Self { mutex }
    }

    pub fn try_download(&self) -> Option<MutexGuard<()>> {
        let guard = self.mutex.try_lock();

        if guard.is_ok() {
            println!("Download started!");
            return guard.ok();
        } else {
            println!("Cannot download since it's already downloading");
            return None;
        }
    }
}

fn main() {
    let test = Test::new();

    // This could be kept alive like you said in an hashmap, so the guard is not dropped
    //  v
    let a = test.try_download(); // Download started!
    let b = test.try_download(); // Cannot download since it's already downloading
}

Why your code doesn't work

There are actually 2 problems

  1. Reference to an unknown stack space (segfault!)
impl<'a> Test<'a> {
    pub fn new() -> Self {
        let mutex = Arc::new(Mutex::new(()));
        //    ^
        //  This reference has the lifetime of mutex
        //          v
        let guard = &mutex.lock().unwrap(); 

        Self {
            mutex, // mutex is moved here, so guard points to something which is at this time
                   // unknown. This can be known only after the new stack frame is built
            guard,
        }
    }
}

  1. where is the mutex really stored? In your example it's on the stackframe of the function new. You then store a pointer to it in a struct and return it. By returning you also drop the stack, so where does the pointer points at? (segfault!)

impl<'a> Test<'a> {
    pub fn new() -> Self {
        let mutex = Arc::new(Mutex::new(()));
        //  MutexGuard is created here and put in the stack dropped when the function exits.
        //           v
        let guard = &mutex.lock().unwrap();

        Self { mutex, guard }
    }
}

Shope answered 17/10, 2023 at 11:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.