Mutex and lifetime


#1

Hi all,

I am having an hard time understanding how to write a function that return a reference to an element inside a lock.

The code is quite simple:

pub struct Loop {
    tx: Sender<r::Command>,
    db: Arc<Mutex<sql::RawConnection>>,
}
impl LoopData for Loop {
    fn get_db(&self) -> &sql::RawConnection {
        &self.db.lock().unwrap()
    }   
}

But I am not sure I understand the error.

 error[E0597]: borrowed value does not live long enough
   --> src/lib.rs:62:10
    |
 62 |         &self.db.lock().unwrap()
    |          ^^^^^^^^^^^^^^^^^^^^^^^ does not live long enough
 63 |     }
    |     - temporary value only lives until here
    |
 note: borrowed value must be valid for the anonymous lifetime #1 defined on the method body at 61:5...
   --> src/lib.rs:61:5
    |
 61 | /     fn get_db(&self) -> &sql::RawConnection {
 62 | |         &self.db.lock().unwrap()
 63 | |     }
    | |_____^
 

The idea would be that loopdata.get_db would take the lock and return a reference to the SQL connection, then when the reference goes out of scope the lock is released.

However, this doesn’t work but I am not quite sure I understood why.

Also here there is a playground version of the same code with the same problem: https://play.rust-lang.org/?gist=250a2d858beebb9eed7a5c14493ad0bf&version=stable

Can anybody point me out in the correct direction?


#2

You can’t do that. The LockGuard returned by .lock() only provides a reference to the connection while it’s active. When it drops at the end of get_db, it unlocks the mutex so you can’t access the connection anymore. You can return a LockGuard<RawConnection> instead of a &RawConnection instead.


#3

Another option is to use a callback, something like:

impl LoopData for Loop {
    fn with_db<R, F: FnOnce(&sql::RawConnection) -> R>(&self, f: F) -> R {
        f(&self.db.lock().unwrap())
    }   
}

#4

@sfackler so as soon as I reference the MutexGuard I will acquire the lock and as soon as the MutexGuard is dropped the lock is released, is that correct?

Your suggestion would be to do something like:

    pub struct Loop {
        tx: Sender<i32>,
        db: Arc<Mutex<i32>>,
    }

    impl Loop {
        fn new(tx: Sender<i32>, db: Arc<Mutex<i32>>) -> Self {
            Loop { tx, db }
        }
        fn get_db(&self) -> MutexGuard<i32> {
            self.db.lock().unwrap()
        }
    }

Correct?

@cuviper Thank you! I really like your approach even if it may not be the best option for my problem right now but it is super elegant and simple! Thanks again :slight_smile:


#5

That’s right. MutexGuard is now the handle that grants you access to the value. You can pass it around like a regular value. You can only access data in it while it’s alive; as soon as it drops, the lock is released. The compiler will ensure this, like in your original code in this thread.

Let me just also mention that the way you do this for Mutex (including @cuviper’s suggestion, which I also always suggest as an alternative) is also how you’d do it for RefCell (which is essentially a single-threaded rwlock).

This is, by the way, a great example of how the compiler and the borrow/ownership system prevent bad mistakes, 100% of the time.


#6

Thank you Vitaly :smile:

Just another quick question, between the creation of the MutexGuard and the first Deref of it the lock is hold or not?

Consider this code

use std::sync::Mutex;

fn main() {
    let v = Mutex::new(5);
    let mut guard_v = v.lock().unwrap();
    // do something <- here the lock is hold or not
    *guard_v += 1; // the lock is definitely hold here since guard_v is in the scope
    println!("v = {}", *guard_v);
}

In do something am I holding the lock or not?


#7

You have the lock as soon as you have the MutexGuard, so basically when the lock() call returns.

One thing worth mentioning is you’ll typically let the guard drop naturally, when it exits its scope. But, the scopes are lexical. So, if you do something expensive before the scope ends, either drop() it manually or put it inside its own smaller scope. For example:

fn main() {
    let v = Mutex::new(5);
    let mut guard_v = v.lock().unwrap();
    // do something <- here the lock is hold or not
    *guard_v += 1; // the lock is definitely hold here since guard_v is in the scope
    println!("v = {}", *guard_v);
    // Lock is going to be held while we sleep!
    // To avoid using extra braces to create scopes, you can do:
    // drop(guard_v);
    std::thread::sleep(Duration::from_secs(10));
}

#8

Then why do I need to deference the MutexGuard to access the internal value?

Is it a naive question?


#9

You don’t need to constantly dereference it. Your code can just as well be:

fn main() {
    let v = Mutex::new(5);
    let mut guard_v = v.lock().unwrap();
    // Pull the mutable reference out into a local - this is safe because the guard outlives this reference
    // and stays alive while we're using the mut reference
    let v = &mut *guard_v;
    *v += 1; 
    println!("v = {}", v);
}

The guard itself represents a particular lock acquisition; the Mutex itself represents guarded data, which you can lock/unlock infinite number of times. But each time you lock, you get a unique guard back which is your “proof” of having the lock acquired. It unlocking on drop is the RAII pattern being applied.

What’s really neat in Rust is the data and the mutex are “embedded” into a single type. In other languages, you’d have a lock and the field it protects as two separate values. You’d then acquire the lock and proceed to use the value. But, there’s absolutely nothing stopping you from using the value without the lock being acquired first. The Rust approach guarantees correct usage.


#10

Sure!

But I was guessing that the deference was like the “trigger” to hold the lock and the drop was the trigger to release it.

So what I am still not getting is why I need to deference it… But maybe you already have explained it and it me a bit slow in understanding!

Your comments are always gold anyway :smiley:


#11

Do you mean why is there a guard wrapper around the value? i.e. what’s the purpose of MutexGuard holding the value rather than somehow being disconnected?

MutexGuard serves the purpose of putting a lifetime constraint on how long you can keep the reference around:

let guard = mutex.lock().unwrap();
let val = &mut *guard; // this is the same thing as `guard.deref_mut()`

val is borrowing from guard, and as such, cannot outlive the guard. This ensures that you cannot retain/smuggle the reference such that it’s kept around after the guard is dropped; the compiler enforces this.

So the guard allows you to “anchor” the borrow against it, and use a reference freely so long as the guard is still alive.

It’s essentially no different from:

struct Person {
   name: String,
}

impl Person {
    fn name(&self) -> &str {
        &self.name
    }
}

That &str is valid only so long as the Person value itself is valid - you cannot retain it beyond that. That’s sort of the relationship between MutexGuard and its inner value. The only “extra” thing here is the MutexGuard's mere existence means you’re also holding the lock to the associated mutex.

Does that answer it?


#12

Yes I believe you answered my question.

What I was wondering is why this code doesn’t work:

use std::sync::Mutex;

fn main() {
    let v = Mutex::new(5);
    let mut guard_v = v.lock().unwrap();
    let foo = guard_v + 1; 
    println!("foo = {}", foo);
}

While this other does compile:

use std::sync::Mutex;

fn main() {
    let v = Mutex::new(5);
    let mut guard_v = v.lock().unwrap();
    let foo = *guard_v + 1;  // <- here change 
    println!("foo = {}", foo);
}

But your explanation completely makes sense! Thanks!


#13

Yeah, it’s simply because MutexGuard is its own type; it’s basically no different than trying to add a String with an i32 - they just don’t support that operation.

In theory, one could add a std::ops::Add impl to MutexGuard, with appropriate generic bounds, such that you can add it and a value of the same type that it holds. I just don’t think these make sense when getting the internal value is already easy via Deref, and then once you have that value, you naturally automatically get the ability to perform whatever operations it defines.