Function Namespaces

Each Function is a Separate Compilation Unit

Functions in Rhai are pure and they form individual compilation units. This means that individual functions can be separated, exported, re-grouped, imported, and generally mix-’n-match-ed with other completely unrelated scripts.

For example, the AST::merge and AST::combine methods (or the equivalent + and += operators) allow combining all functions in one AST into another, forming a new, unified, group of functions.

In general, there are two types of namespaces where functions are looked up:

NamespaceHow ManySourceLookupSub-modules?Variables?
GlobalOne1) AST being evaluated
2) Engine::register_XXX API
3) global modules registered via Engine::register_global_module
4) functions in static modules registered via Engine::register_static_module and marked global
simple nameignoredignored
ModuleMany1) Module registered via Engine::register_static_module
2) Module loaded via import statement
namespace-qualified nameyesyes

Module Namespaces

There can be multiple module namespaces at any time during a script evaluation, usually loaded via the import statement.

Static module namespaces can also be registered into an Engine via Engine::register_static_module.

Functions and variables in module namespaces are isolated and encapsulated within their own environments.

They must be called or accessed in a namespace-qualified manner.


#![allow(unused)]
fn main() {
import "my_module" as m;            // new module namespace 'm' created via 'import'

let x = m::calc_result();           // namespace-qualified function call

let y = m::MY_NUMBER;               // namespace-qualified variable (constant) access

let x = calc_result();              // <- error: function 'calc_result' not found
                                    //    in global namespace!
}

Global Namespace

There is one global namespace for every Engine, which includes (in the following search order):

  • All functions defined in the AST currently being evaluated.

  • All native Rust functions and iterators registered via the Engine::register_XXX API.

  • All functions and iterators defined in global modules that are registered into the Engine via Engine::register_global_module.

  • Functions defined in modules registered via Engine::register_static_module that are specifically marked for exposure to the global namespace (e.g. via the #[rhai(global)] attribute in a plugin module).

Anywhere in a Rhai script, when a function call is made, the function is searched within the global namespace, in the above search order.

Therefore, function calls in Rhai are late bound – meaning that the function called cannot be determined or guaranteed and there is no way to lock down the function being called. This aspect is very similar to JavaScript before ES6 modules.


#![allow(unused)]
fn main() {
// Compile a script into AST
let ast1 = engine.compile(
    r#"
        fn get_message() {
            "Hello!"                // greeting message
        }

        fn say_hello() {
            print(get_message());   // prints message
        }

        say_hello();
    "#
)?;

// Compile another script with an overriding function
let ast2 = engine.compile(r#"fn get_message() { "Boo!" }"#)?;

// Combine the two AST's
ast1 += ast2;                       // 'message' will be overwritten

engine.consume_ast(&ast1)?;         // prints 'Boo!'
}

Therefore, care must be taken when cross-calling functions to make sure that the correct functions are called.

The only practical way to ensure that a function is a correct one is to use modules - i.e. define the function in a separate module and then import it:


#![allow(unused)]
fn main() {
----------------
| message.rhai |
----------------

fn get_message() { "Hello!" }


---------------
| script.rhai |
---------------

import "message" as msg;

fn say_hello() {
    print(msg::get_message());
}
say_hello();
}