5.0 KiB
Function Namespaces
{{#include ../links.md}}
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
method allows merging all functions in one [AST
] into another,
forming a new, combined, group of functions.
In general, there are two types of namespaces where functions are looked up:
Namespace | Source | Lookup method | How Many |
---|---|---|---|
Global | Engine::register_XXX API, [AST ] being evaluated, [packages] loaded |
Simple function name | One |
Module | [Module ] |
Namespace-qualified function name | As many as [import ]-ed |
Global Namespace
There is one global namespace for every [Engine
], which includes:
-
All the native Rust functions registered via the
Engine::register_XXX
API. -
All the Rust functions defined in [packages] that are loaded into the [
Engine
].
In addition, during evaluation of an [AST
], all script-defined functions bundled together within
the [AST
] are added to the global namespace and override any existing registered functions of
the same names and number of parameters.
Anywhere in a Rhai script, when a function call is made, it is searched within the global namespace. 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.
// Compile a script into AST
let ast1 = engine.compile(
r#"
fn message() { "Hello!" } // greeting message
fn say_hello() {
print(message()); // prints message
}
say_hello();
"#
)?;
// Compile another script with an overriding function
let ast2 = engine.compile(r#"fn message() { "Boo!" }"#)?;
// Merge the two AST's
let ast = ast1.merge(ast2); // 'message' will be overwritten
engine.consume_ast(&ast)?; // 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:
----------------
| message.rhai |
----------------
fn message() { "Hello!" }
---------------
| script.rhai |
---------------
fn say_hello() {
import "message" as msg;
print(msg::message());
}
say_hello();
Module Namespaces
[Modules] can be dynamically loaded into a Rhai script using the [import
] keyword.
When that happens, functions defined within the [module] can be called with a qualified name.
There is a catch, though, if functions in a module script refer to global functions defined within the script. When called later, those functions will be searched in the current global namespace and may not be found.
-----------------
| greeting.rhai |
-----------------
fn message() { "Hello!" };
fn say_hello() { print(message()); }
say_hello(); // 'message' is looked up in the global namespace
---------------
| script.rhai |
---------------
import "greeting" as g;
g::say_hello(); // <- error: function not found - 'message'
In the example above, although the module greeting.rhai
loads fine ("Hello!"
is printed),
the subsequent call using the namespace-qualified function name fails to find the same function
'message
' which now essentially becomes g::message
. The call fails as there is no more
function named 'message
' in the global namespace.
Therefore, when writing functions for a [module], make sure that those functions are as pure as possible and avoid cross-calling them from each other. A [function pointer] is a valid technique to call another function within a module-defined function:
-----------------
| greeting.rhai |
-----------------
fn message() { "Hello!" };
fn say_hello(msg_func) { // 'msg_func' is a function pointer
print(msg_func.call()); // call via the function pointer
}
say_hello(); // 'message' is looked up in the global namespace
---------------
| script.rhai |
---------------
import "greeting" as g;
fn my_msg() {
import "greeting" as g; // <- must import again here...
g::message() // <- ... otherwise will not find module 'g'
}
g::say_hello(Fn("my_msg")); // prints 'Hello!'