Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Borrow data out of a mutex "borrowed value does not live long enough"

Tags:

rust

How can I return an iterator over data within a mutex which itself is contained within a struct. The error the compiler gives is "borrowed value does not live long enough".

How do I get the lifetime of the value to extend into the outer scope?

Here is a minimal demo of what I am trying to achieve.

use std::sync::{Mutex, Arc};
use std::vec::{Vec};
use std::slice::{Iter};

#[derive(Debug)]
struct SharedVec {
  pub data: Arc<Mutex<Vec<u32>>>,
}

impl SharedVec {
  fn iter(& self) -> Iter<u32> {
    self.data.lock().unwrap().iter()
  }
}

fn main() {

  let sv = SharedVec {
    data: Arc::new(Mutex::new(vec![1, 2, 3, 4, 5]))
  };

  for element in sv.data.lock().unwrap().iter() {  // This works
    println!("{:?}", element);
  }

  for element in sv.iter() {  // This does not work
    println!("{:?}", element);
  }
}

Rust playground link: http://is.gd/voukyN

like image 436
Luke Avatar asked Aug 18 '15 21:08

Luke


2 Answers

You cannot do it exactly how you have written here.

Mutexes in Rust use RAII pattern for acquisition and freeing, that is, you acquire a mutex when you call the corresponding method on it which returns a special guard value. When this guard goes out of scope, the mutex is released.

To make this pattern safe Rust uses its borrowing system. You can access the value inside the mutex only through the guard returned by lock(), and you only can do so by reference - MutexGuard<T> implements Deref<Target=T> and DerefMut<Target=T>, so you can get &T or &mut T out of it.

This means that every value you derive from a mutexed value will necessarily have its lifetime linked to the lifetime of the guard. However, in your case you're trying to return Iter<u32> with its lifetime parameter tied to the lifetime of self. The following is the full signature of iter() method, without lifetime parameters elision, and its body with explicit temporary variables:

fn iter<'a>(&'a self) -> Iter<'a, u32> {
    let guard = self.data.lock().unwrap();
    guard.iter()
}

Here the lifetime of guard.iter() result is tied to the one guard, which is strictly smaller than 'a because guard only lives inside the scope of the method body. This is a violation of borrowing rules, and so the compiler fails with an error.

When iter() returns, guard is destroyed and the lock is released, so Rust in fact prevented you from making an actual logical error! The same code in C++ would compile and behave incorrectly because you would access protected data without locking it, causing data races at the very least. Just another demonstration of the power of Rust :)

I don't think you'll be able to do what you want without nasty hacks or boilerplate wrappers around standard types. And I personally think this is good - you have to manage your mutexes as explicit as possible in order to avoid deadlocks and other nasty concurrency problems. And Rust already makes your life much easier because it enforces absence of data races through its borrowing system, which is exactly the reason why the guard system behaves as described above.

like image 199
Vladimir Matveev Avatar answered Sep 25 '22 12:09

Vladimir Matveev


As Vladimir Matveev's answer mentions, this isn't possible with return values. You can achieve your goal if you pass the iterator into a function instead of returning it:

impl SharedVec {
    fn iter<R>(&self, func: impl FnOnce(Iter<'_, u32>) -> R) -> R {
        let guard = self.data.lock().unwrap();
        func(guard.iter())
    }
}

This function is used like this:

sv.iter(|iter| {
    for element in iter {
        println!("{:?}", element);
    }
});

This type of function wrapping will have to be repeated with every type of iterator. If you end up doing that, it may be easier to hand over a mutable slice or &mut SharedVec instead, making the closure choose the iteration method.

This method works because you never release the lock keeping the data protected from multiple threads from writing at the same time.

like image 32
5hammer Avatar answered Sep 23 '22 12:09

5hammer