Specifying a lifetime for a Mutex lock

Hello!

In my application I need a static Mutex<Vec<Struct>>. To simplify the usage of this mutex I wanted to add helper methods for getting a specific struct. Because I acquire the lock to the mutex inside my helper function it gets dropped before I can use the returned reference to the struct. I could clone the struct, but I'd like to avoid that.

use lazy_static::lazy_static; // 1.4.0
use std::sync::Mutex;

lazy_static! {
    static ref ITEMS: Mutex<Vec<i32>> = Mutex::new(Vec::new());
}

fn get_item<'a>(x: i32) -> Option<&'a i32> {
    let items = ITEMS.lock().unwrap();
    
    items.iter().find(|item| **item == x)
}

pub fn main() {
    ITEMS.lock().unwrap().push(20);
    ITEMS.lock().unwrap().push(30);
    
    dbg!(get_item(20));
}

I tried to explicitly set the type of items to MutexGuard<'a, Vec<Struct>>, but this doesn't work.

Is there any way to specify the lifetime of the lock?

Playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=ecf844c0bd6b0d5e45244ef4e1ff9864

Comments in code:

use lazy_static::lazy_static; // 1.4.0
use std::sync::{Mutex, MutexGuard};

lazy_static! {
    static ref ITEMS: Mutex<Vec<i32>> = Mutex::new(Vec::new());
}

fn get_item<'a>(lock: &'a MutexGuard<'_, Vec<i32>>, x: i32) -> Option<&'a i32> {
    // when you lock the mutex here, the lock will last only til the
    // end of this closure
    // let items = ITEMS.lock().unwrap();
    
    // Yet, you are trying to return an item that requires the above lock
    lock.iter().find(|item| **item == x)
    // Thus, when the closure ends here, the lock gets dropped and the
    // returned item becomes dangling. Rust helps prevent dangling pointers
    // by giving you the compile error: "cannot return value referencing local variable `items`"
}

pub fn main() {
    let mut lock = ITEMS.lock().expect("Unable to lock mutex");
    lock.push(20);
    lock.push(30);
    
    dbg!(get_item(&lock, 20));
}

Lock the mutex in the main function, and pass a mutable pointer of that lock into the get_item function (which will allow you to reuse it again). IF you lock it inside the get_item function, the lock drops and the pointer invalidated

Thank you!

Is there a way to specify the lifetime of the lock? I was thinking more like something like this:

use lazy_static::lazy_static; // 1.4.0
use std::sync::Mutex;

lazy_static! {
    static ref ITEMS: Mutex<Vec<i32>> = Mutex::new(Vec::new());
}

fn get_item<'a>(x: i32) -> Option<&'a i32> {
    let items = ITEMS.lock<'a>().unwrap(); // CHANGE
    
    items.iter().find(|item| **item == x)
}

pub fn main() {
    ITEMS.lock().unwrap().push(20);
    ITEMS.lock().unwrap().push(30);
    
    dbg!(get_item(20));
}

This would make the function so much easier to use. I didn't really find a way to pass a lifetime to the lock function.

The reference to MutexGuard lasts for <'a>, and the return item lasts for <'a> as well. The items pointed-to by the MutexGuard last for static because you have it inside a lazy_static block

In the code I sent, it shows how you can do that. You need to specify the lifetime <'a> in the function. When you do this, wherever you call it (in the main function) implies the calling closure has lifetime <'a>. When you call the get_item function, the function lasts for a shorter period of time than <'a> since it's popped into existence, then pops out of existence once the get_item function ends. in other words, whereas main lasts for <'a> amount, get_item will last for (implicitly) <'b>.

With the exception of static lifetimes, explicit lifetimes like a or b or c are relative to the closure you are in.

First in your code you do not need reference, you can (even more efficiently) return i32 by value.

If this is just sample and really need to reference something realize that reference is only valid as long as you have exclusive lock, eg lifetime of MutexGuard. Otherwise some other thread can modify Vec and reference may become invalid (if Vec is reallocated. So you just must pass ref to MutexGuard into get_item. You cannot extend lifetime of item created in function scope. First rule of lifetime annotations - you cannot use it to extend lifetime, just to identify existing one!

You don't set a lifetime. You structure your code correctly, and the lifetimes come from that. In fact, correct Rust code can be compiled without any knowledge of what lifetimes are whatsoever (although you can't reject invalid Rust code without understanding it).

In your specific case, just return an owned integer.

fn get_item(x: i32) -> Option<i32> {
    let items = ITEMS.lock().unwrap();
    
    items.iter().find(|item| **item == x).map(|i| *i)
}
2 Likes

You could also write a function that takes a closure and that accepts a Option<Struct> and call the function with the closure. That way you wouldn't have to deal with the mutex guard each time.
If that's a good option however really depends on what you want to do.

1 Like

You cannot extend a borrow beyond the life of the thing it references. When a mutex guard goes out of scope, or is otherwise dropped, it unlocks the mutex. If you could access the value after that, the mutex wouldn't be mutually excluding concurrent access to its contained value anymore. Lifetimes are only annotations. They tell you which value a reference is bound to, and which other lifetimes they have to outlive, or not outlive, but they do not extend or shorten the lives of the variables they are attached to.

If you need the mutex guard to live longer, you will have to take the lock higher up the callstack, or return the MutexGuard itself from your function (as long as that doesn't cause it to outlive the Mutex itself!).

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.