Issue with trait generics and static mut context

#1

Hi guys,

My apologies for the title, I’m not sure how to concisely to describe this problem!

I’m working on a project that is mostly intended for web uses, with the exception of some console based tests I use for writing/debugging. I initially ran into an issue where I had to figure out how to dynamically choose a random number generator implementation at runtime since the thread_rng was not available on the web and the wasm_rng wasn’t available from the console. So I came up with a simple wrapper trait that had a single function that allowed me to generate random numbers from an underlying source random number generator, like this:

pub trait RngWrapper {
    fn gen_range(&self, min: usize, max: usize) -> usize;
}
...
pub struct ThreadRng;
impl RngWrapper for ThreadRng {
    fn gen_range(&self, min: usize, max: usize) -> usize {
        rand::thread_rng().gen_range(min, max)
    }
}
...
pub struct WasmRng;
impl RngWrapper for WasmRng {
    fn gen_range(&self, min: usize, max: usize) -> usize {
        wasm_rng().gen_range(min, max)
    }
}

This worked well for me and up till now, I’ve been using it successfully in other traits as follows:

pub trait MazeAlgorithm: Debug {
    fn on(&self, grid: &dyn Grid, rng_generator: &dyn RngWrapper);
}
...

pub trait Grid {
    ...    
    fn random_cell(&self, rng: &dyn RngWrapper) -> Option<ICellStrong>;
    ...
}

Where the MazeAlgorithm is called with either RNG depending on the context (test fn or web fn).

Again - the above has worked great. However, I now need to access another RNG function - shuffle, which takes &mut Vec<T> as a parameter. Unfortunately, as written that all appears to fail miserably when I try to add this to the RngWrapper trait:

fn shuffle<T>(&self, vec: &mut Vec<T>);

As I’ve learned from https://doc.rust-lang.org/book/ch17-02-trait-objects.html#object-safety-is-required-for-trait-objects, I of course get the trait rng::RngWrapper cannot be made into an object, note: method shuffle has generic type parameters.

I took the approach of changing all the &dyn to impl and that got me almost all the way there, except for one wrinkle I’m not sure how to handle.

For the web side of things, I need to store and reuse the grid later to do different things. I was previously doing it like this at the root of the library and accessing it in unsafe blocks:

static mut GRID:  Option<Box<dyn Grid>> = None;

The above static mut GRID no longer works with the updated version using impl. I’m a bit stumped on how to fix this problem. Any ideas on how I might fix this?

0 Likes

#2

How does the Grid trait relate to the RngWrapper trait? Perhaps it can be solved with something else like associated types for a predetermined input to the shuffle method.


Whoops, sorry! I missed the part about the random_cell method. I’d really like to help answer this question, but unfortunately I’m on mobile :confused:

0 Likes

#3

Alright, you can turn it into something like this:

trait RngWrapper {
    type Shuffle;
    fn gen_range(/**/);
    fn shuffle(&self, data: &mut Vec<Self::Shuffle>);
}
trait Grid {
    fn random_cell(&self, rng: &dyn mut RngWrapper<Shuffle=Something>) -> Option<ICellStrong>;
}
0 Likes

#4

Interesting! I forgot about those completely, I’ll look back at the book for some more info on it. That looks like it should work, thanks for the help!

0 Likes

#5

No problem! Associated types are pretty useful for things like this.

1 Like

#6

Ok, while skimming at your post, I saw:

static mut GRID:  Option<Box<dyn Grid>> = None;

which automatically triggered my writing this: static mut is a very very unsafe feature (much more than most of us would imagine), because of immutability guarantees Rust uses whenever it sees a shared reference (i.e., whenever you unsafe { &GRID } it is Undefined Behavior to ever unsafe { &mut GRID} afterwards).

Avoiding Undefined Behavior

Using unsafe (not recommended unless you know what you are doing)

If you want to go on the unsafe mutable global path, you should use:

struct GlobalGrid /* = */ (::std::cell::UnsafeCell< // UnsafeCell for `mut`
    Box<dyn Grid>
>);

impl GlobalGrid {
    /// # Safety
    ///
    /// You must ensure no other `get_mut` borrows are active
    unsafe fn get (&'_ self) -> &'_ dyn Grid
    {
        & **self.0.get()
    }

    /// # Safety
    ///
    /// You must ensure no other borrows are active
    unsafe fn get_mut (&'_ self) -> &'_ mut dyn Grid
    {
        &mut **self.0.get()
    }
}

unsafe impl Sync for GlobalGrid {}

static GRID: Option<GlobalGrid> = None;

This way at least once your borrow of unsafe { GRID.get() } ends you can go back to using unsafe { GRID.get_mut() }.

The safe way(s)

Multithreaded global

Use ::lazy_static or ::once_cell.

Example with ::lazy_static
use ::std::sync::RwLock;

::lazy_static::lazy_static! {
    static ref GRID: RwLock< Option<Box<dyn Grid>> > = RwLock::new(None);
}

And that’s it. To get an immutable reference you use GRID.read().expect("lock was poisoned") and to get a mutable reference you use GRID.write().expect("lock was poisoned").

Thread-local global

The syntax is uglier, but the performance may be slightly better

use ::std::cell::RefCell;

thread_local! {
    static GRID: RefCell< Option<Box<dyn Grid>> > = RefCell::new(None);
}

To get an immutable reference you use:

GRID.with(|slf| match slf.borrow() { grid_ref => {
    // you can use grid_ref as &_ here
}})

And for a mutable reference:

GRID.with(|slf| match slf.borrow_mut() { grid_ref_mut => {
    // you can use grid_ref_mut as &mut _ here
}})
2 Likes

#7

Wow, I still have a lot to learn! I’m definitely going to try those safe methods you mentioned. To be honest… and maybe its just my imagination and these aren’t actually related, but I noticed 2 things immediately after I put in the static mut GRID: 1) My computer started having strange issues when working on this code. E.g., my start button would stop working, some programs wouldn’t open etc. Happens almost every time I work on this code and I have to reboot. 2) I notice that sometimes I get wasm out of memory errors on the web version after enough webpack reloads. :grimacing:

@Yandros Do you happen to know of any other recommended references/guidance on safe usage of globals so I can learn more? Thanks for the information!

0 Likes

#8

static mut, in practice, should not cause such issues, it looks more like some program in your computer is leaking memory; I don’t use webpack so I have no clue as to how to debug that;

Regarding safe usage of globals, I haven’t stumbled upon a nice summarizing post yet (I’m about to write a blog post about it myself), but my previous post is a practical TL,DR about it.

It’s actually closely related to Sync and multithreading, since a global is like a shared reference (&_) being given to every function in every thread, so whatever you may find about sharing data accross threads should be applicable to globals (c.f. RwLock above and the pervasive Mutex).

Mutation through shared references is really the topic of Rust’s interior mutability, so that is definitely the direction you should be looking for when wanting mutation of globals.

To better understand how you can have mutation and aliasing (shared references), I will mention @mbrubeck’s excellent Rust: A unique perspective, where they very concisely describe the challenges of what they call Shared Mutability (scroll down to approx. 60%)

The only thing I did not mention at all in my previous post is when the globals are just plain integers:

  • in a thread-local scenario, you can use Cell<{integer}> (e.g., Cell<usize>), read the value with let n: usize = cell.get(); and write into it with cell.set(42_usize);

  • in a multi-threaded scenario, you can use AtomicUsize, read the value with let n: usize = atomic.load(Ordering::SeqCst), and write into it with atomic.store(42_usize, Ordering::SeqCst)

0 Likes