Rust shared mutable global - April 2023
Rust shared Mutable
During my experiments in wasm I wanted global state stored.
My discoveries along the way won’t be news to old hands, but I wanted a Cribsheet to remind me of the journey.
The aim: Global wasm state
Since it is global is should be static.
Since its static it can only use const operators.
For instance, Arc::new is not.
16 | pub static TEST_STATE : Arc<Option<GuiState>>= Arc::new(None);
| ^^^^^^^^^^^^^^
|
= note: calls in statics are limited to constant functions, tuple structs and tuple variants
= note: consider wrapping this expression in `Lazy::new(|| ...)` from the `once_cell` crate: https://crates.io/crates/once_cell
But Mutex::new() and RwLock::new() are. There are also other crates as it says.
The solution, use RwLock
I want to get used to threading API’s so I’m not going to defend this. Which means I can use a:
pub static GUI_STATE: RwLock<Option<GuiState>> = RwLock::new(None);
How to make it a Some?
Once I have the web page load up all the reference data I can set the state up. The incantation is:
let mut w: RwLockWriteGuard<Option<GuiState>> = GUI_STATE.write().unwrap();
*w = Some(GuiState::new(data));
Note that the less verbose version below will not work, as GUI_STATE is a static and cannot be borrowed mutably.
*GUI_STATE.get_mut().unwrap() = Some(gs); // won't work for me.
So, a *w above, ie * on an RwLockWriteGuard, does what exactly? It calls a deref function if one is available, and looking at the code it is:
#[stable(feature = "rust1", since = "1.0.0")]
impl<T: ?Sized> Deref for RwLockWriteGuard<'_, T> {
type Target = T;
fn deref(&self) -> &T {
// SAFETY: the conditions of `RwLockWriteGuard::new` were satisfied when created.
unsafe { &*self.lock.data.get() }
}
}
#[stable(feature = "rust1", since = "1.0.0")]
impl<T: ?Sized> DerefMut for RwLockWriteGuard<'_, T> {
fn deref_mut(&mut self) -> &mut T {
// SAFETY: the conditions of `RwLockWriteGuard::new` were satisfied when created.
unsafe { &mut *self.lock.data.get() }
}
}
So *w is calling DerefMut and we can then assign the Some holding our new data.
What if the data in the some has to be shared?
So, Arc is a thread safe way of sharing data, which is immutable. So our GuiState struct could use that, to share it through the system.
struct GuiState {
pub active_data_flow: Option<Arc<DataFlow>>
}
Note that the data in an Arc is immutable. So this optional active_data_flow has to be Option<Arc… and not Arc<Option..
How to read the state data
Long hand version first:
let gui_binding: RwLockReadGuard<Option<GuiState>> = GUI_STATE.read().unwrap();
// Just to highlight the types here
let gui_data: &Option<GuiState> = gui_binding.deref();
match gui_data {
Some(gui_state) => {
func(gui_state)
}
None => {
web_sys::console::log_1(&format!("No gui state").into());
}
}
Don’t forget *gui_binding will also call deref, so, adding the types, look at this:
let gui_data: &Option<GuiState> = gui_binding.deref();
let gui_data1: &Option<GuiState> = &*gui_binding;
So, this also works:
let gui_binding: RwLockReadGuard<Option<GuiState>> = GUI_STATE.read().unwrap();
if let Some(gs) = &*gui_binding {
func(gs)
}
Note that we cannot do this:
if let Some(gs) = *gui_binding { // BAD as it will try to move, not ref
func(&gs)
}
So, moving to a shorter form we get
if let Some(gs) = &*GUI_STATE.read().unwrap() {
func(gs)
}
How to update the state data
So, the user wants to set or unset the active data flow:
struct GuiState {
pub active_data_flow: Option<Arc<DataFlow>>
}
We already saw the mutable deref on the RwLock guard - see above.
So, this works:
Lessons
Arc shares immutable data.
Mutex and RwLock share mutable data