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
throughout the script makes it slow and cumbersome. -
Using
global
or a variable resolver defeats constants propagation in script optimization.
-
The key to global constants is to use them to optimize 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 the
AST
. -
This pattern also works under Strict Variables Mode.
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)?;