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:
Namespace | How Many | Source | Lookup | Sub-modules? | Variables? |
---|---|---|---|---|---|
Global | One | 1) AST being evaluated2) Engine::register_XXX API3) global modules registered via Engine::register_global_module 4) functions in static modules registered via Engine::register_static_module and marked global | simple name | ignored | ignored |
Module | Many | 1) Module registered via Engine::register_static_module 2) Module loaded via import statement | namespace-qualified name | yes | yes |
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
viaEngine::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(); }