This repository has been archived on 2025-08-04. You can view files and clone it, but cannot push or open issues or pull requests.
rhaj/rhai_engine/rhaibook/patterns/constants.md
2025-04-03 09:18:05 +02:00

4.0 KiB
Raw Blame History

Global Constants

{{#include ../links.md}}


* Script has a lot of duplicated [constants] used inside [functions].

* For easier management, [constants] are declared at the top of the script.

* As Rhai [functions] are pure, they cannot access [constants] declared at global level
  except through [`global`].

* Sprinkling large number of [`global::CONSTANT`][`global`] throughout the script makes
  it slow and cumbersome.

* Using [`global`] or a [variable resolver] defeats
  [constants propagation]({{rootUrl}}/engine/optimize/constants.md) in [script optimization].

* The key to global [constants] is to use them to [optimize][script optimization] a script.
  Otherwise, it would be just as simple to pass the constants into a custom [`Scope`] instead.

* The script is first compiled into an [`AST`], and all [constants] are extracted.

* The [constants] are then supplied to [re-optimize][script optimization] the [`AST`].

* This pattern also works under [_Strict Variables Mode_][strict variables].

Example

Assume that the following Rhai script needs to work (but it doesn't).

// These are constants

const FOO = 1;
const BAR = 123;
const MAGIC_NUMBER = 42;

fn get_magic() {
    MAGIC_NUMBER        // <- oops! 'MAGIC_NUMBER' not found!
}

fn calc_foo(x) {
    x * global::FOO     // <- works but cumbersome; not desirable!
}

let magic = get_magic() * BAR;

let x = calc_foo(magic);

print(x);

Step 1 Compile Script into AST

Compile the script into [AST] form.

Normally, it is useful to disable [optimizations][script optimization] at this stage since the [AST] will be re-optimized later.

[Strict Variables Mode][strict variables] must be OFF for this to work.

// Turn Strict Variables Mode OFF (if necessary)
engine.set_strict_variables(false);

// Turn optimizations OFF
engine.set_optimization_level(OptimizationLevel::None);

let ast = engine.compile("...")?;

Step 2 Extract Constants

Use AST::iter_literal_variables to extract top-level [constants] from the [AST].

let mut scope = Scope::new();

// Extract all top-level constants without running the script
ast.iter_literal_variables(true, false).for_each(|(name, _, value)|
    scope.push_constant(name, value);
);

// 'scope' now contains: FOO, BAR, MAGIC_NUMBER

Step 3a Propagate Constants

[Re-optimize][script optimization] the [AST] using the new constants.

// Turn optimization back ON
engine.set_optimization_level(OptimizationLevel::Simple);

let ast = engine.optimize_ast(&scope, ast, engine.optimization_level());

Step 3b Recompile Script (Alternative)

If [Strict Variables Mode][strict variables] is used, however, it is necessary to re-compile the script in order to detect undefined [variable] usages.

// Turn Strict Variables Mode back ON
engine.set_strict_variables(true);

// Turn optimization back ON
engine.set_optimization_level(OptimizationLevel::Simple);

// Re-compile the script using constants in 'scope'
let ast = engine.compile_with_scope(&scope, "...")?;

Step 4 Run the Script

At this step, the [AST] is now optimized with constants propagated into all access sites.

The script essentially becomes:

// These are constants

const FOO = 1;
const BAR = 123;
const MAGIC_NUMBER = 42;

fn get_magic() {
    42      // <- constant replaced by value
}

fn calc_foo(x) {
    x * global::FOO
}

let magic = get_magic() * 123;  // <- constant replaced by value

let x = calc_foo(magic);

print(x);

Run it via Engine::run_ast or Engine::eval_ast.

// The 'scope' is no longer necessary
engine.run_ast(&ast)?;