Export a Rust Module to Rhai

Prelude

When using the plugins system, the entire rhai::plugin module must be imported as a prelude because code generated will need these imports.


#![allow(unused)]
fn main() {
use rhai::plugin::*;
}

#[export_module]

When applied to a Rust module, the #[export_module] attribute generates the necessary code and metadata to allow Rhai access to its public (i.e. marked pub) functions, constants and sub-modules.

This code is exactly what would need to be written by hand to achieve the same goal, and is custom fit to each exported item.

All pub functions become registered functions, all pub constants become module constant variables, and all sub-modules become Rhai sub-modules.

This Rust module can then be registered into an Engine as a normal module. This is done via the exported_module! macro.

The macro combine_with_exported_module! can be used to combine all the functions and variables into an existing module, flattening the namespace – i.e. all sub-modules are eliminated and their contents promoted to the top level. This is typical for developing custom packages.


#![allow(unused)]
fn main() {
use rhai::plugin::*;        // a "prelude" import for macros

#[export_module]
mod my_module {
    // This constant will be registered as the constant variable 'MY_NUMBER'.
    // Ignored when registered as a global module.
    pub const MY_NUMBER: i64 = 42;

    // This function will be registered as 'greet'.
    pub fn greet(name: &str) -> String {
        format!("hello, {}!", name)
    }
    // This function will be registered as 'get_num'.
    pub fn get_num() -> i64 {
        mystic_number()
    }
    // This function will be registered as 'increment'.
    // It will also be exposed to the global namespace since 'global' is set.
    #[rhai_fn(global)]
    pub fn increment(num: &mut i64) {
        *num += 1;
    }
    // This function is not 'pub', so NOT registered.
    fn mystic_number() -> i64 {
        42
    }

    // Sub-modules are ignored when the module is registered globally.
    pub mod my_sub_module {
        // This function is ignored when registered globally.
        // Otherwise it is a valid registered function under a sub-module.
        pub fn get_info() -> String {
            "hello".to_string()
        }
    }

    // Sub-modules are commonly used to put feature gates on a group of
    // functions because feature gates cannot be put on function definitions.
    // This is currently a limitation of the plugin procedural macros.
    #[cfg(feature = "advanced_functions")]
    pub mod advanced {
        // This function is ignored when registered globally.
        // Otherwise it is a valid registered function under a sub-module
        // which only exists when the 'advanced_functions' feature is used.
        pub fn advanced_calc(input: i64) -> i64 {
            input * 2
        }
    }
}
}

Use Engine::register_global_module

The simplest way to register this into an Engine is to first use the exported_module! macro to turn it into a normal Rhai module, then use the Engine::register_global_module method on it:

fn main() {
    let mut engine = Engine::new();

    // The macro call creates a Rhai module from the plugin module.
    let module = exported_module!(my_module);

    // A module can simply be registered into the global namespace.
    engine.register_global_module(module.into());
}

The functions contained within the module definition (i.e. greet, get_num and increment) are automatically registered into the Engine when Engine::register_global_module is called.


#![allow(unused)]
fn main() {
let x = greet("world");
x == "hello, world!";

let x = greet(get_num().to_string());
x == "hello, 42!";

let x = get_num();
x == 42;

increment(x);
x == 43;
}

Notice that, when using a module as a package, only functions registered at the top level can be accessed.

Variables as well as sub-modules are ignored.

Use Engine::register_static_module

Another simple way to register this into an Engine is, again, to use the exported_module! macro to turn it into a normal Rhai module, then use the Engine::register_static_module method on it:

fn main() {
    let mut engine = Engine::new();

    // The macro call creates a Rhai module from the plugin module.
    let module = exported_module!(my_module);

    // A module can simply be registered as a static module namespace.
    engine.register_static_module("service", module.into());
}

The functions contained within the module definition (i.e. greet, get_num and increment), plus the constant MY_NUMBER, are automatically registered under the module namespace service:


#![allow(unused)]
fn main() {
let x = service::greet("world");
x == "hello, world!";

service::MY_NUMBER == 42;

let x = service::greet(service::get_num().to_string());
x == "hello, 42!";

let x = service::get_num();
x == 42;

service::increment(x);
x == 43;
}

All functions (usually methods) defined in the module and marked with #[rhai_fn(global)], as well as all type iterators, are automatically exposed to the global namespace, so iteration, getters/setters and indexers for custom types can work as expected.

In fact, the default for all getters/setters and indexers defined in a plugin module is #[rhai_fn(global)] unless specifically overridden by #[rhai_fn(internal)].

Therefore, in the example above, the increment method (defined with #[rhai_fn(global)]) works fine when called in method-call style:


#![allow(unused)]
fn main() {
let x = 42;
x.increment();
x == 43;
}

Use Dynamically

Using this directly as a dynamically-loadable Rhai module is almost the same, except that a module resolver must be used to serve the module, and the module is loaded via import statements.

See the module section for more information.

Combine into Custom Package

Finally the plugin module can also be used to develop a custom package, using combine_with_exported_module!:


#![allow(unused)]
fn main() {
def_package!(rhai:MyPackage:"My own personal super package", module, {
    combine_with_exported_module!(module, "my_module_ID", my_module));
});
}

combine_with_exported_module! automatically flattens the module namespace so that all functions in sub-modules are promoted to the top level. This is convenient for custom packages.

Sub-Modules and Feature Gates

Sub-modules in a plugin module definition are turned into valid sub-modules in the resultant Rhai Module.

They are also commonly used to put feature gates or compile-time gates on a group of functions, because currently attributes do not work on individual function definitions due to a limitation of the procedural macros system.

This is especially convenient when using the combine_with_exported_module! macro to develop custom packages because selected groups of functions can easily be included or excluded based on different combinations of feature flags instead of having to manually include/exclude every single function.


#![allow(unused)]
fn main() {
#[export_module]
mod my_module {
    // Always available
    pub fn func0() {}

    // The following sub-module is only available under 'feature1'
    #[cfg(feature = "feature1")]
    pub mod feature1 {
        fn func1() {}
        fn func2() {}
        fn func3() {}
    }

    // The following sub-module is only available under 'feature2'
    #[cfg(feature = "feature2")]
    pub mod feature2 {
        fn func4() {}
        fn func5() {}
        fn func6() {}
    }
}

// Registered functions:
//   func0 - always available
//   func1, func2, func3 - available under 'feature1'
//   func4, func5, func6 - available under 'feature2'
combine_with_exported_module!(module, "my_module_ID", my_module);
}

Function Overloading and Operators

Operators and overloaded functions can be specified via applying the #[rhai_fn(name = "...")] attribute to individual functions.

The text string given as the name parameter to #[rhai_fn] is used to register the function with the Engine, disregarding the actual name of the function.

With #[rhai_fn(name = "...")], multiple functions may be registered under the same name in Rhai, so long as they have different parameters.

Operators (which require function names that are not valid for Rust) can also be registered this way.

Registering the same function name with the same parameter types will cause a parsing error.


#![allow(unused)]
fn main() {
use rhai::plugin::*;        // a "prelude" import for macros

#[export_module]
mod my_module {
    // This is the '+' operator for 'TestStruct'.
    #[rhai_fn(name = "+")]
    pub fn add(obj: &mut TestStruct, value: i64) {
        obj.prop += value;
    }
    // This function is 'calc (i64)'.
    #[rhai_fn(name = "calc")]
    pub fn calc_with_default(num: i64) -> i64 {
        ...
    }
    // This function is 'calc (i64, bool)'.
    #[rhai_fn(name = "calc")]
    pub fn calc_with_option(num: i64, option: bool) -> i64 {
        ...
    }
}
}

Getters, Setters and Indexers

Functions can be marked as getters/setters and indexers for custom types via the #[rhai_fn] attribute, which is applied on a function level.


#![allow(unused)]
fn main() {
use rhai::plugin::*;        // a "prelude" import for macros

#[export_module]
mod my_module {
    // This is a normal function 'greet'.
    pub fn greet(name: &str) -> String {
        format!("hello, {}!", name)
    }
    // This is a getter for 'TestStruct::prop'.
    #[rhai_fn(get = "prop", pure)]
    pub fn get_prop(obj: &mut TestStruct) -> i64 {
        obj.prop
    }
    // This is a setter for 'TestStruct::prop'.
    #[rhai_fn(set = "prop")]
    pub fn set_prop(obj: &mut TestStruct, value: i64) {
        obj.prop = value;
    }
    // This is an index getter for 'TestStruct'.
    #[rhai_fn(index_get)]
    pub fn get_index(obj: &mut TestStruct, index: i64) -> bool {
        obj.list[index]
    }
    // This is an index setter for 'TestStruct'.
    #[rhai_fn(index_set)]
    pub fn get_index(obj: &mut TestStruct, index: i64, state: bool) {
        obj.list[index] = state;
    }
}
}

Multiple Registrations

Parameters to the #[rhai_fn(...)] attribute can be applied multiple times.

This is especially useful for the name = "...", get = "..." and set = "..." parameters to give multiple alternative names to the same function.


#![allow(unused)]
fn main() {
use rhai::plugin::*;        // a "prelude" import for macros

#[export_module]
mod my_module {
    // This function can be called in five ways
    #[rhai_fn(name = "get_prop_value", name = "prop", name = "+", set = "prop", index_get)]
    pub fn prop_function(obj: &mut TestStruct, index: i64) -> i64 {
        obj.prop[index]
    }
}
}

The above function can be called in five ways:

Parameter for #[rhai_fn(...)]TypeCall style
name = "get_prop_value"method functionget_prop_value(x, 0), x.get_prop_value(0)
name = "prop"method functionprop(x, 0), x.prop(0)
name = "+"operatorx + 42
set = "prop"setterx.prop = 42
index_getindex getterx[0]

Pure Functions

Apply the #[rhai_fn(pure)] attribute on a method function (i.e. one taking a &mut first parameter) to mark it as pure.

Pure functions MUST NOT modify the value of the &mut parameter.

Therefore, pure functions can be passed a constant value as the first &mut parameter.

Non-pure functions, when passed a constant value as the first &mut parameter, will raise an EvalAltResult::ErrorAssignmentToConstant error.

For example:


#![allow(unused)]
fn main() {
use rhai::plugin::*;        // a "prelude" import for macros

#[export_module]
mod my_module {
    fn internal_calc(array: &mut rhai::Array, x: i64) -> i64 {
        array.iter().map(|v| v.as_int().unwrap()).fold(0, |(r, v)| r += v * x)
    }
    // This function can be passed a constant
    #[rhai_fn(name = "add1", pure)]
    pub fn add_scaled(array: &mut rhai::Array, x: i64) -> i64 {
        internal_calc(array, x)
    }
    // This function CANNOT be passed a constant
    #[rhai_fn(name = "add2")]
    pub fn add_scaled2(array: &mut rhai::Array, x: i64) -> i64 {
        internal_calc(array, x)
    }
    // This getter can be applied to a constant
    #[rhai_fn(get = "first1", pure)]
    pub fn get_first(array: &mut rhai::Array) -> i64 {
        array[0]
    }
    // This getter CANNOT be applied to a constant
    #[rhai_fn(get = "first2")]
    pub fn get_first2(array: &mut rhai::Array) -> i64 {
        array[0]
    }
    // The following is a syntax error because a setter is SUPPOSED to
    // mutate the object.  Therefore the 'pure' attribute cannot be used.
    #[rhai_fn(get = "values", pure)]
    pub fn set_values(array: &mut rhai::Array, value: i64) {
        // ...
    }
}
}

When applied to a Rhai script:


#![allow(unused)]
fn main() {
// Constant
const VECTOR = [1, 2, 3, 4, 5, 6, 7];

let r = VECTOR.add1(2);     // ok!

let r = VECTOR.add2(2);     // runtime error: constant modified

let r = VECTOR.first1;      // ok!

let r = VECTOR.first2;      // runtime error: constant modified
}

Fallible Functions

To register fallible functions (i.e. functions that may return errors), apply the #[rhai_fn(return_raw)] attribute on functions that return Result<T, Box<EvalAltResult>> where T is any clonable type.

A syntax error is generated if the function with #[rhai_fn(return_raw)] does not have the appropriate return type.


#![allow(unused)]
fn main() {
use rhai::plugin::*;        // a "prelude" import for macros

#[export_module]
mod my_module {
    // This overloads the '/' operator for i64.
    #[rhai_fn(name = "/", return_raw)]
    pub fn double_and_divide(x: i64, y: i64) -> Result<i64, Box<EvalAltResult>> {
        if y == 0 {
            Err("Division by zero!".into())
        } else {
            Ok((x * 2) / y)
        }
    }
}
}

NativeCallContext Parameter

The first parameter of a function can also be NativeCallContext, which is treated specially by the plugins system.

#[export_module] Parameters

Parameters can be applied to the #[export_module] attribute to override its default behavior.

ParameterDescription
noneexports only public (i.e. pub) functions
export_allexports all functions (including private, non-pub functions); use #[rhai_fn(skip)] on individual functions to avoid export
export_prefix = "..."exports functions (including private, non-pub functions) with names starting with a specific prefix

Inner Attributes

Inner attributes can be applied to the inner items of a module to tweak the export process.

#[rhai_fn] is applied to functions, while #[rhai_mod] is applied to sub-modules.

Parameters should be set on inner attributes to specify the desired behavior.

Attribute ParameterUse withApply toDescription
skip#[rhai_fn], #[rhai_mod]function or sub-moduledo not export this function/sub-module
global#[rhai_fn]functionexpose this function to the global namespace
internal#[rhai_fn]functionkeep this function within the internal module namespace
name = "..."#[rhai_fn], #[rhai_mod]function or sub-moduleregisters function/sub-module under the specified name
get = "..."#[rhai_fn]pub fn (&mut Type) -> Valueregisters a getter for the named property
set = "..."#[rhai_fn]pub fn (&mut Type, Value)registers a setter for the named property
index_get#[rhai_fn]pub fn (&mut Type, INT) -> Valueregisters an index getter
index_set#[rhai_fn]pub fn (&mut Type, INT, Value)registers an index setter
return_raw#[rhai_fn]pub fn (...) -> Result<Type, Box<EvalAltResult>>marks this as a fallible function
pure#[rhai_fn]pub fn (&mut Type, ...) -> ...marks this as a pure function