Script Optimization

Rhai includes an optimizer that tries to optimize a script after parsing. This can reduce resource utilization and increase execution speed.

Script optimization can be turned off via the no_optimize feature.

Dead Code Removal

For example, in the following:


#![allow(unused_variables)]
fn main() {
{
    let x = 999;            // NOT eliminated: variable may be used later on (perhaps even an 'eval')
    123;                    // eliminated: no effect
    "hello";                // eliminated: no effect
    [1, 2, x, x*2, 5];      // eliminated: no effect
    foo(42);                // NOT eliminated: the function 'foo' may have side-effects
    666                     // NOT eliminated: this is the return value of the block,
                            // and the block is the last one so this is the return value of the whole script
}
}

Rhai attempts to eliminate dead code (i.e. code that does nothing, for example an expression by itself as a statement, which is allowed in Rhai).

The above script optimizes to:


#![allow(unused_variables)]
fn main() {
{
    let x = 999;
    foo(42);
    666
}
}

Constants Propagation

Constants propagation is used to remove dead code:


#![allow(unused_variables)]
fn main() {
const ABC = true;
if ABC || some_work() { print("done!"); }   // 'ABC' is constant so it is replaced by 'true'...
if true || some_work() { print("done!"); }  // since '||' short-circuits, 'some_work' is never called
if true { print("done!"); }                 // <- the line above is equivalent to this
print("done!");                             // <- the line above is further simplified to this
                                            //    because the condition is always true
}

These are quite effective for template-based machine-generated scripts where certain constant values are spliced into the script text in order to turn on/off certain sections.

For fixed script texts, the constant values can be provided in a user-defined Scope object to the Engine for use in compilation and evaluation.

Watch Out for Function Calls

Beware, however, that most operators are actually function calls, and those functions can be overridden, so they are not optimized away:


#![allow(unused_variables)]
fn main() {
const DECISION = 1;

if DECISION == 1 {          // NOT optimized away because you can define
    :                       // your own '==' function to override the built-in default!
    :
} else if DECISION == 2 {   // same here, NOT optimized away
    :
} else if DECISION == 3 {   // same here, NOT optimized away
    :
} else {
    :
}
}

because no operator functions will be run (in order not to trigger side-effects) during the optimization process (unless the optimization level is set to OptimizationLevel::Full).

So, instead, do this:


#![allow(unused_variables)]
fn main() {
const DECISION_1 = true;
const DECISION_2 = false;
const DECISION_3 = false;

if DECISION_1 {
    :                       // this branch is kept and promoted to the parent level
} else if DECISION_2 {
    :                       // this branch is eliminated
} else if DECISION_3 {
    :                       // this branch is eliminated
} else {
    :                       // this branch is eliminated
}
}

In general, boolean constants are most effective for the optimizer to automatically prune large if-else branches because they do not depend on operators.

Alternatively, turn the optimizer to OptimizationLevel::Full.