I have a scenario like this : Rust Playground
Container
with a lock that is protecting multiple members. I don't want RwLock per member.complex_mutation
that does some complex logic and mutates this struct._guard
for lock acquires a reference and then calling another function complex_mutation
where &mut self
is passed results in multiple immutable + mutable references.complex_mutation
this will work but that would be too ugly for a real world scenario.This is a common pattern in C++. How do I solve this in RUST?
You seem to be confused about a few things. Let me try to clarify.
()
, which is pointless.&mut self
then locks don't help you. Locks are one of many mechanisms that allow you to get an exclusive reference (&mut
) from a shared reference (&
). The ability to write to a value when you start with a shared reference is called "interior mutability" and it is implemented by RwLock
, and also by Mutex
, RefCell
, etc. If you are starting with an exclusive reference already, interior mutability is redundant.With that out of the way, what you want to do is put the data in your type into the lock. You can do this with a tuple, but a better idea would be a private "inner" type, like this:
struct Container {
lock: RwLock<ContainerInner>,
}
struct ContainerInner {
pub data1: u32,
pub data2: u32,
}
Now, you want to take &self
instead.
If you have methods that need to operate on the data in ContainerInner
because you call them from multiple locations, you can simply put them on ContainerInner
instead, like this:
impl Container {
pub fn update_date(&self, val1: u32, val2: u32) {
self.lock.write().unwrap().complex_mutation(val1, val2);
}
}
impl ContainerInner {
fn complex_mutation(&mut self, val1: u32, val2: u32) {
self.data1 = val1;
self.data2 = val2;
}
}
Note that if you have a &mut self
then you don't actually need to lock; RwLock::get_mut()
allows you to get a &mut
to the inner value if you have a &mut
to the lock without locking because having an &mut
to the lock is a static guarantee that the value isn't shared.
If it helps you conceptually, &
and &mut
work kind of like RwLock
's read()
and write()
respectively -- you can have many &
or a single &mut
, and if you have a &mut
you can't have a &
. (This is somewhat simplified, but you get the idea.) Unlike with runtime locks, the Rust compiler checks that these are used correctly at compile time, which means no runtime checks or locks are needed.
So you could add a method like this:
impl Container {
pub fn update_date_mut(&mut self, val1: u32, val2: u32) {
self.lock.get_mut().unwrap().complex_mutation(val1, val2);
}
}
This is exactly the same as the other method except we take &mut self
which allows us to use get_mut()
in place of write()
, bypassing the lock. The compiler will not let you call this method unless it can prove the value isn't shared, so it is safe to use if the compiler lets you use it. Otherwise, you need to use the other method, which will lock.