Module Resolvers
See the section on Importing Modules for more details.
When encountering an import
statement, Rhai attempts to resolve the module based on the path string.
Module Resolvers are service types that implement the ModuleResolver
trait.
Set into Engine
An Engine
’s module resolver is set via a call to Engine::set_module_resolver
:
use rhai::module_resolvers::{DummyModuleResolver, StaticModuleResolver};
// Create a module resolver
let resolver = StaticModuleResolver::new();
// Register functions into 'resolver'...
// Use the module resolver
engine.set_module_resolver(resolver);
// Effectively disable 'import' statements by setting module resolver to
// the 'DummyModuleResolver' which acts as... well... a dummy.
engine.set_module_resolver(DummyModuleResolver::new());
Built-in Module Resolvers
There are a number of standard resolvers built into Rhai, the default being the FileModuleResolver
which simply loads a script file based on the path (with .rhai
extension attached) and execute it
to form a module.
Built-in module resolvers are grouped under the rhai::module_resolvers
module namespace.
DummyResolversCollection
(default for no-std
)
This module resolver acts as a dummy and fails all module resolution calls.
FileModuleResolver
(normal default)
The default module resolution service, not available for no_std
or WASM builds.
Loads a script file (based off the current directory or a specified one) with .rhai
extension.
Function namespace
All functions in the global namespace, plus all those defined in the same module, are merged into a unified namespace.
All modules imported at global level via import
statements become sub-modules,
which are also available to functions defined within the same script file.
Base directory
If the base directory is not set, then relative paths are based off the directory of the loading script.
This allows scripts to simply cross-load each other.
Relative paths are resolved relative to a root directory, which is usually the base directory.
The base directory can be set via FileModuleResolver::new_with_path
or FileModuleResolver::set_base_path
.
Custom Scope
The set_scope
method adds an optional Scope
which will be used to optimize module scripts.
Caching
By default, modules are also cached so a script file is only evaluated once, even when repeatedly imported.
Unix Shebangs
On Unix-like systems, the shebang (#!
) is used at the very beginning of a script file to mark a
script with an interpreter (for Rhai this would be rhai-run
).
If a script file starts with #!
, the entire first line is skipped.
Because of this, Rhai scripts with shebangs at the beginning need no special processing.
#!/home/to/me/bin/rhai-run
// This is a Rhai script
let answer = 42;
print(`The answer is: ${answer}`);
Example
┌────────────────┐
│ my_module.rhai │
└────────────────┘
// This function overrides any in the main script.
private fn inner_message() { "hello! from module!" }
fn greet() {
print(inner_message()); // call function in module script
}
fn greet_main() {
print(main_message()); // call function not in module script
}
┌───────────┐
│ main.rhai │
└───────────┘
// This function is overridden by the module script.
fn inner_message() { "hi! from main!" }
// This function is found by the module script.
fn main_message() { "main here!" }
import "my_module" as m;
m::greet(); // prints "hello! from module!"
m::greet_main(); // prints "main here!"
Simulate virtual functions
When calling a namespace-qualified function defined within a module, other functions defined within the same module script override any similar-named functions (with the same number of parameters) defined in the global namespace. This is to ensure that a module acts as a self-contained unit and functions defined in the calling script do not override module code.
In some situations, however, it is actually beneficial to do it in reverse: have module code call functions defined in the calling script (i.e. in the global namespace) if they exist, and only call those defined in the module script if none are found.
One such situation is the need to provide a default implementation to a simulated virtual function:
┌────────────────┐
│ my_module.rhai │
└────────────────┘
// Do not do this (it will override the main script):
// fn message() { "hello! from module!" }
// This function acts as the default implementation.
private fn default_message() { "hello! from module!" }
// This function depends on a 'virtual' function 'message'
// which is not defined in the module script.
fn greet() {
if is_def_fn("message", 0) { // 'is_def_fn' detects if 'message' is defined.
print(message());
} else {
print(default_message());
}
}
┌───────────┐
│ main.rhai │
└───────────┘
// The main script defines 'message' which is needed by the module script.
fn message() { "hi! from main!" }
import "my_module" as m;
m::greet(); // prints "hi! from main!"
┌────────────┐
│ main2.rhai │
└────────────┘
// The main script does not define 'message' which is needed by the module script.
import "my_module" as m;
m::greet(); // prints "hello! from module!"
StaticModuleResolver
StaticModuleResolver
is often used with no_std
in embedded environments
without a file system.
Loads modules that are statically added.
Functions are searched in the global namespace by default.
use rhai::{Module, module_resolvers::StaticModuleResolver};
let module: Module = create_a_module();
let mut resolver = StaticModuleResolver::new();
resolver.insert("my_module", module);
ModuleResolversCollection
A collection of module resolvers.
Modules are resolved from each resolver in sequential order.
This is useful when multiple types of modules are needed simultaneously.