Static struct with hashmap mut issue

I am using wasm and need some global state. Hence static struct with a hashmap in it. I can't seem to find a way of getting mutable access to the hashmap to insert stuff into it ... any help appreciated.

use std::collections::HashMap;
use nalgebra::Point3;

pub struct Shell {
    pub points: Option<HashMap<Point3<u16>, bool>>,
}

static mut static_shell:Shell = Shell {
        points: None,
};

fn main() {

    unsafe {
        static_shell.points = Some(HashMap::new());
    
        if let Some(mut points) = &static_shell.points {
            points.insert(Point3::new(1,2,3), true);
            println!("{:?}", points);
        }
    }
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0507]: cannot move out of a shared reference
  --> src/main.rs:20:35
   |
20 |         if let Some(mut points) = &static_shell.points {
   |                     ----------    ^^^^^^^^^^^^^^^^^^^^
   |                     |
   |                     data moved here
   |                     move occurs because `points` has type `HashMap<OPoint<u16, Const<3_usize>>, bool>`, which does not implement the `Copy` trait

For more information about this error, try `rustc --explain E0507`.
error: could not compile `playground` due to previous error

Oh sorry, the solution is similar to a previous question I posted and it was already solved by @alice. Here is one possible solution:

use std::collections::HashMap;
use nalgebra::Point3;

pub struct Shell {
    pub points: Option<HashMap<Point3<u16>, bool>>,
}

static mut STATIC_SHELL:Shell = Shell {
        points: None,
};

fn main() {

    unsafe {
        STATIC_SHELL.points = Some(HashMap::new());
        if let Some(points) = &mut STATIC_SHELL.points {
            points.insert(Point3::new(1,2,3), true);
            println!("{:?}", points);
        }   
    }
}

You shouldn't be using static mut without synchronization. Really, you shouldn't be using static mut at all. There is a reason it's unsafe: since a static is global, you can have many threads accessing it at the same time. If at least one of them mutates the static without synchronization, you've got yourself instant undefined behavior.

Use once_cell::sync::Lazy<Mutex<T>> instead.

2 Likes

Thanks @H2CO3 - as I am in a wasm/web assembly framework I am limited to a single thread.

I believe static mut is the way to maintain state across the bridge between js and rust …

&UnsafeCell is safer than static mut, if you actually have a use case that requires such globals.

See

2 Likes

It is not the way. It is a possible way, but again, you shouldn't be using it. You likely misunderstood my suggestion. You can still put a Lazy<Mutex<_>> into a global static, you just don't have to use unsafe, as the static itself can then be non-mut (and yet you can mutate it thanks to the wrapping mutex). Hence, you can maintain global state in it perfectly fine:

static GLOBAL_MAP: Lazy<Mutex<HashMap<String, String>>> = Lazy::new(Mutex::default);

fn main() {
    GLOBAL_MAP.lock().unwrap().insert("key".to_owned(), "value".to_owned());
    
    println!("{:?}", GLOBAL_MAP.lock().unwrap().get("key"));
}

As for why static mut is likely not the right tool even when not using threads: well, you might be (or think you are) in a single-threaded environment, but:

  1. That might change in the future (e.g. the wasm execution environment becomes multi-threaded, or you simply re-use the code elsewhere), and the compiler won't be able to catch the lack of synchronization, so it's extremely dangerous and not future-proof; and
  2. Threading is not the only reason for shared mutability being incorrect; shared mutability of references, for example, is always UB in Rust even within a single thread. In fact, static mut has the potential of being deprecated exactly because it's borderline impossible to use it correctly.
5 Likes