Global Constants
* 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 at this stage since
the AST
will be re-optimized later.
Strict Variables Mode 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 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 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)?;