Rust tip that the mutex with lifetime
Today I encounter a case to return the reference from one struct method like this:
Return reference from the struct's method
#[test]
fn single_lifetime_test() {
struct MyStruct {
vec: Vec<i32>,
}
impl MyStruct {
fn get_element<'a>(&'a self, index: usize) -> Option<&'a i32> {
self.vec.get(index)
}
}
let my_struct = MyStruct {
vec: vec![1, 2, 3, 4, 5],
};
if let Some(val) = mystruct.get_element(2) {
println!("The value is: {}", val);
} else {
println!("No value found at that index");
}
}
If you want to return the reference from the struct's method, you must specify the lifetime to indicate the reference's lifetime is equals to the struct self at least. So 'a
is the tag of lifetime.
But something is strange when using the mutex to wrap this struct.
With mutex to wrap the struct self
Sometimes, we will provide a thread safe object to access for external invoking that is wrapped by the mutex or lock to ensure the thread safe internally. Like this:
struct Buffer {
internal: RwLock<BufferInternal>
}
struct BufferInternal {
data: Vec<i32>
}
impl Buffer {
pub fn get_data_ref<'a>(&'a self, index: usize) -> Option<&'a i32> {
let buffer = self.internal.read().unwrap();
buffer.data.get(index)
}
}
The compile error has been shown above screenshot, which means the reference has been held by the lock guard. Once the scope is end, the ref is illegal, which looks strange. But something could be more simple to explain.
Lock guard effects the reference lifetime
#[test]
fn single_lifetime_test() {
struct MyStruct {
vec: Vec<i32>,
}
impl MyStruct {
fn get_element<'a>(&'a self, index: usize) -> Option<&'a i32> {
self.vec.get(index)
}
}
let my_struct = MyStruct {
vec: vec![1, 2, 3, 4, 5],
};
let locked = RwLock::new(my_struct);
if let Some(val) = locked.read().unwrap().get_element(2) {
println!("The value is: {}", val);
} else {
println!("No value found at that index");
}
}
The same error is thrown, which directly indicates the lock guard scope is ended but the ref is still alive that breaks down the Rust RAII rule.
This could be optimzed by the following way:
#[test]
fn single_lifetime_test() {
struct MyStruct {
vec: Vec<i32>,
}
impl MyStruct {
fn get_element<'a>(&'a self, index: usize) -> Option<&'a i32> {
self.vec.get(index)
}
}
let my_struct = MyStruct {
vec: vec![1, 2, 3, 4, 5],
};
let locked = RwLock::new(my_struct);
let guarder = locked.read().unwrap();
if let Some(val) = guarder.get_element(2) {
println!("The value is: {}", val);
} else {
println!("No value found at that index");
}
}
Solution or best practise
ref: https://stackoverflow.com/questions/70538333/how-do-i-return-a-reference-to-t-within-rwlockt
The object could not be wrapped into the lock internal generics, which should follow with the guarder, the following code like this:
#[test]
fn lifetime_with_lock_test() {
struct MyStruct {
vec: Vec<i64>,
}
struct LockedStruct {
lock: RwLock<i64>,
inner: MyStruct
}
impl LockedStruct {
fn get_ref(&self) -> &Vec<i64> {
let _guarder = self.lock.read().unwrap();
&self.inner.vec
}
}
let data = LockedStruct {
lock: Default::default(),
inner: MyStruct { vec: vec![1, 2, 3] },
};
assert_eq!(3, data.get_ref().len());
}