Rust tip that the mutex with lifetime

#rust

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)
	}
}

Pasted image 20240612165532.png

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");  
	}  
}

Pasted image 20240612165841.png
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());  
}