Implement a Custom Module Resolver

For many applications in which Rhai is embedded, it is necessary to customize the way that modules are resolved. For instance, modules may need to be loaded from script texts stored in a database, not in the file system.

A module resolver must implement the ModuleResolver trait, which contains only one required function: resolve.

When Rhai prepares to load a module, ModuleResolver::resolve is called with the name of the module path (i.e. the path specified in the import statement).

Success

Upon success, it should return a shared module wrapped by Rc (or Arc under sync).

The module resolver should call Module::build_index on the target module before returning it.

  • This method flattens the entire module tree and indexes it for fast function name resolution.
  • If the module is already indexed, calling this method has no effect.

Failure

  • If the path does not resolve to a valid module, return EvalAltResult::ErrorModuleNotFound.

  • If the module failed to load, return EvalAltResult::ErrorInModule.

Example of a Custom Module Resolver

use rhai::{ModuleResolver, Module, Engine, EvalAltResult};

// Define a custom module resolver.
struct MyModuleResolver {}

// Implement the 'ModuleResolver' trait.
impl ModuleResolver for MyModuleResolver {
    // Only required function.
    fn resolve(
        &self,
        engine: &Engine,                        // reference to the current 'Engine'
        source_path: Option<&str>,              // path of the parent module
        path: &str,                             // the module path
        pos: Position,                          // position of the 'import' statement
    ) -> Result<Rc<Module>, Box<EvalAltResult>> {
        // Check module path.
        if is_valid_module_path(path) {
            // Load the custom module
            match load_secret_module(path) {
                Ok(my_module) => {
                    my_module.build_index();    // index it
                    Rc::new(my_module)          // make it shared
                },
                // Return 'EvalAltResult::ErrorInModule' upon loading error
                Err(err) => Err(EvalAltResult::ErrorInModule(path.into(), Box::new(err), pos).into())
            }
        } else {
            // Return 'EvalAltResult::ErrorModuleNotFound' if the path is invalid
            Err(EvalAltResult::ErrorModuleNotFound(path.into(), pos).into())
        }
    }
}

let mut engine = Engine::new();

// Set the custom module resolver into the 'Engine'.
engine.set_module_resolver(MyModuleResolver {});

engine.run(
r#"
    import "hello" as foo;  // this 'import' statement will call
                            // 'MyModuleResolver::resolve' with "hello" as 'path'
    foo:bar();
"#)?;

Advanced – ModuleResolver::resolve_ast

There is another function in the ModuleResolver trait, resolve_ast, which is a low-level API intended for advanced usage scenarios.

ModuleResolver::resolve_ast has a default implementation that simply returns None, which indicates that this API is not supported by the module resolver.

Any module resolver that serves modules based on Rhai scripts should implement ModuleResolver::resolve_ast. When called, the compiled AST of the script should be returned.

ModuleResolver::resolve_ast should not return an error if ModuleResolver::resolve will not. On the other hand, the same error should be returned if ModuleResolver::resolve will return one.