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