Mutable Global State

Don’t Do It™

Generations of programmers struggled to get around mutable global state (a.k.a. the `window` object) in the initial design of JavaScript.

In contrast to global constants, mutable global states are strongly discouraged because:

  1. It is a sure-fire way to create race conditions – that is why Rust does not support it;

  2. It adds considerably to debug complexity – it is difficult to reason, in large code bases, where/when a state value is being modified;

  3. It forces hard (but obscure) dependencies between separate pieces of code that are difficult to break when the need arises;

  4. It is almost impossible to add new layers of redirection and/or abstraction afterwards without major surgery.

This is not something that Rhai encourages. _You Have Been Warned™_. There are two ways...

Option 1 – Get/Set Functions

This is similar to the Control Layer pattern.

Use get/set functions to read/write the global mutable state.

// The globally mutable shared value let value = Rc::new(RefCell::new(42)); // Register an API to access the globally mutable shared value let v = value.clone(); engine.register_fn("get_global_value", move || *v.borrow()); let v = value.clone(); engine.register_fn("set_global_value", move |value: i64| *v.borrow_mut() = value);

These functions can be used in script functions to access the shared global state.

fn foo() { let current = get_global_value(); // Get global state value current += 1; set_global_value(current); // Modify global state value }

This option is preferred because it is possible to modify the get/set functions later on to add/change functionalities without introducing breaking script changes.

Option 2 – Variable Resolver

Declare a variable resolver that returns a shared value which is the global state.

// Use a shared value as the global state let value: Dynamic = 1.into(); let mut value = value.into_shared(); // convert into shared value // Clone the shared value let v = value.clone(); // Register a variable resolver. engine.on_var(move |name, _, _| { match name "value" => Ok(Some(v.clone())), _ => Ok(None) } }); // The shared global state can be modified *value.write_lock::<i64>().unwrap() = 42;

The global state variable can now be used just like a normal local variable, including modifications.

fn foo() { value = value * 2; // ^ global variable can be read // ^ global variable can also be modified }
This option makes mutable global state so easy to implement that it should actually be considered an _Anti-Pattern_.