rhai/doc/src/language/fn-namespaces.md
2020-12-07 21:54:52 +08:00

4.4 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 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 method Sub-modules? Variables?
Global One 1) [AST] being evaluated
2) Engine::register_XXX API
3) [packages] loaded
4) functions in [modules] loaded via Engine::register_module and marked global
simple function name ignored ignored
Module Many [Module] namespace-qualified function name yes yes

Module Namespace

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

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.

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 [packages] that are loaded into the [Engine].

  • Functions defined in [modules] loaded via Engine::register_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.

// 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:

----------------
| message.rhai |
----------------

fn get_message() { "Hello!" }


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

import "message" as msg;

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