Compile a Script (to AST)

To repeatedly evaluate a script, compile it first with Engine::compile into an AST (Abstract Syntax Tree) form.

Engine::eval_ast_XXX and Engine::run_ast_XXX evaluate a pre-compiled AST.

// Compile to an AST and store it for later evaluations
let ast = engine.compile("40 + 2")?;

for _ in 0..42 {
    let result: i64 = engine.eval_ast(&ast)?;

    println!("Answer #{i}: {result}");      // prints 42
}

Tip: Compile script file

Compiling script files is also supported via Engine::compile_file (not available for no_std or WASM builds).

let ast = engine.compile_file("hello_world.rhai".into())?;

See also: AST manipulation API

Advanced users who may want to manipulate an AST, especially the functions contained within, should see the section on Manage AST’s for more details.

Practical Use – Header Template Scripts

Sometimes it is desirable to include a standardized header template in a script that contains pre-defined functions, constants and imported modules.

// START OF THE HEADER TEMPLATE
// The following should run before every script...

import "hello" as h;
import "world" as w;

// Standard constants

const GLOBAL_CONSTANT = 42;
const SCALE_FACTOR = 1.2;

// Standard functions

fn foo(x, y) { ... }

fn bar() { ... }

fn baz() { ... }

// END OF THE HEADER TEMPLATE

// Everything below changes from run to run

foo(bar() + GLOBAL_CONSTANT, baz() * SCALE_FACTOR)

Option 1 – The easy way

Prepend the script header template onto independent scripts and run them as a whole.

Pros: Easy!

Cons: If the header template is long, work is duplicated every time to parse it.

let header_template = "..... // scripts... .....";

for index in 0..10000 {
    let user_script = db.get_script(index);

    // Just merge the two scripts...
    let combined_script = format!("{header_template}\n{user_script}\n");

    // Run away!
    let result = engine.eval::<i64>(combined_script)?;
    
    println!("{result}");
}

Option 2 – The hard way

Option 1 requires the script header template to be recompiled every time. This can be expensive if the header is very long.

This option compiles both the script header template and independent scripts as separate AST’s which are then joined together to form a combined AST.

Pros: No need to recompile the header template!

Cons: More work…

let header_template = "..... // scripts... .....";

let mut template_ast = engine.compile(header_template)?;

// If you don't want to run the template, only keep the functions
// defined inside (e.g. closures), clear out the statements.
template_ast.clear_statements();

for index in 0..10000 {
    let user_script = db.get_script(index);

    let user_ast = engine.compile(user_script)?;

    // Merge the two AST's
    let combined_ast = template_ast + user_ast;

    // Run away!
    let result = engine.eval_ast::<i64>(combined_ast)?;
    
    println!("{result}");

Option 3 – The not-so-hard way

Option 1 does repeated work, option 2 requires manipulating AST’s…

This option makes the scripted functions (not imported modules nor constants however) available globally by first making it a module (via Module::eval_ast_as_new) and then loading it into the Engine via Engine::register_global_module.

Pros: No need to recompile the header template!

Cons: No imported modules nor constants; if the header template is changed, a new Engine must be created.

let header_template = "..... // scripts... .....";

let template_ast = engine.compile(header_template)?;

let template_module = Module::eval_ast_as_new(Scope::new(), &template_ast, &engine)?;

engine.register_global_module(template_module.into());

for index in 0..10000 {
    let user_script = db.get_script(index);

    // Run away!
    let result = engine.eval::<i64>(user_script)?;
    
    println!("{result}");
}