rhai/doc/src/plugins/function.md
2021-01-01 17:05:06 +08:00

7.7 KiB
Raw Blame History

Export a Rust Function to Rhai

{{#include ../links.md}}

Sometimes only a few ad hoc functions are required and it is simpler to register individual functions instead of a full-blown [plugin module].

Macros

Macro Signature Description
#[export_fn] apply to rust function defined in a Rust module exports the function
register_exported_fn! register_exported_fn!(&mut engine, "name", function) registers the function into an [Engine] under a specific name
set_exported_fn! set_exported_fn!(&mut module, "name", function) registers the function into a [Module] under a specific name
set_exported_global_fn! set_exported_global_fn!(&mut module, "name", function) registers the function into a [Module] under a specific name, exposing it to the global namespace

#[export_fn] and register_exported_fn!

Apply #[export_fn] onto a function defined at module level to convert it into a Rhai plugin function.

The function cannot be nested inside another function it can only be defined directly under a module.

To register the plugin function, simply call register_exported_fn!. The name of the function can be any text string, so it is possible to register overloaded functions as well as operators.

use rhai::plugin::*;        // import macros

#[export_fn]
fn increment(num: &mut i64) {
    *num += 1;
}

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

    // 'register_exported_fn!' registers the function as 'inc' with the Engine.
    register_exported_fn!(engine, "inc", increment);
}

Fallible Functions

To register [fallible functions] (i.e. functions that may return errors), apply the #[rhai_fn(return_raw)] attribute on plugin functions that return Result<Dynamic, Box<EvalAltResult>>.

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

use rhai::plugin::*;        // a "prelude" import for macros

#[export_fn]
#[rhai_fn(return_raw)]
pub fn double_and_divide(x: i64, y: i64) -> Result<Dynamic, Box<EvalAltResult>> {
    if y == 0 {
        Err("Division by zero!".into())
    } else {
        let result = (x * 2) / y;
        Ok(result.into())
    }
}

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

    // Overloads the operator '+' with the Engine.
    register_exported_fn!(engine, "+", double_and_divide);
}

NativeCallContext Parameter

If the first parameter of a function is of type rhai::NativeCallContext, then it is treated specially by the plugins system.

NativeCallContext is a type that encapsulates the current native call context and exposes the following:

Field Type Description
engine() &Engine the current [Engine], with all configurations and settings.
This is sometimes useful for calling a script-defined function within the same evaluation context using [Engine::call_fn][call_fn], or calling a [function pointer].
source() Option<&str> reference to the current source, if any
iter_imports() impl Iterator<Item = (&str, &Module)> iterator of the current stack of [modules] imported via import statements
imports() &Imports reference to the current stack of [modules] imported via import statements; requires the [internals] feature
iter_namespaces() impl Iterator<Item = &Module> iterator of the namespaces (as [modules]) containing all script-defined functions
namespaces() &[&Module] reference to the namespaces (as [modules]) containing all script-defined functions; requires the [internals] feature

This first parameter, if exists, will be stripped before all other processing. It is virtual. Most importantly, it does not count as a parameter to the function and there is no need to provide this argument when calling the function in Rhai.

The native call context can be used to call a [function pointer] or [closure] that has been passed as a parameter to the function, thereby implementing a callback:

use rhai::{Dynamic, FnPtr, NativeCallContext, EvalAltResult};
use rhai::plugin::*;        // a "prelude" import for macros

#[export_fn]
#[rhai_fn(return_raw)]
pub fn greet(context: NativeCallContext, callback: FnPtr)
                            -> Result<Dynamic, Box<EvalAltResult>>
{
    // Call the callback closure with the current context
    // to obtain the name to greet!
    let name = callback.call_dynamic(context, None, [])?;
    Ok(format!("hello, {}!", name).into())
}

The native call context is also useful in another scenario: protecting a function from malicious scripts.

use rhai::{Dynamic, Array, NativeCallContext, EvalAltResult, Position};
use rhai::plugin::*;        // a "prelude" import for macros

// This function builds an array of arbitrary size, but is protected
// against attacks by first checking with the allowed limit set
// into the 'Engine'.
#[export_fn]
#[rhai_fn(return_raw)]
pub fn grow(context: NativeCallContext, size: i64)
                            -> Result<Dynamic, Box<EvalAltResult>>
{
    // Make sure the function does not generate a
    // data structure larger than the allowed limit
    // for the Engine!
    if size as usize > context.engine().max_array_size()
    {
        return EvalAltResult::ErrorDataTooLarge(
            "Size to grow".to_string(),
            context.engine().max_array_size(),
            size as usize,
            Position::NONE,
        ).into();
    }

    let array = Array::new();

    for x in 0..size {
        array.push(x.into());
    }

    OK(array.into())
}