Call Rhai Functions from Rust

Rhai also allows working backwards from the other direction – i.e. calling a Rhai-scripted function from Rust via Engine::call_fn.

┌─────────────┐
│ Rhai script │
└─────────────┘

import "process" as proc;           // this is evaluated every time

fn hello(x, y) {
    // hopefully 'my_var' is in scope when this is called
    x.len + y + my_var
}

fn hello(x) {
    // hopefully 'my_string' is in scope when this is called
    x * my_string.len()
}

fn hello() {
    // hopefully 'MY_CONST' is in scope when this is called
    if MY_CONST {
        proc::process_data(42);     // can access imported module
    }
}


┌──────┐
│ Rust │
└──────┘

// Compile the script to AST
let ast = engine.compile(script)?;

// Create a custom 'Scope'
let mut scope = Scope::new();

// A custom 'Scope' can also contain any variables/constants available to
// the functions
scope.push("my_var", 42_i64);
scope.push("my_string", "hello, world!");
scope.push_constant("MY_CONST", true);

// Evaluate a function defined in the script, passing arguments into the
// script as a tuple.
//
// Beware, arguments must be of the correct types because Rhai does not
// have built-in type conversions. If arguments of the wrong types are passed,
// the Engine will not find the function.
//
// Variables/constants pushed into the custom 'Scope'
// (i.e. 'my_var', 'my_string', 'MY_CONST') are visible to the function.

let result: i64 = engine.call_fn(&mut scope, &ast, "hello", ( "abc", 123_i64 ) )?;
//          ^^^                                             ^^^^^^^^^^^^^^^^^^
//          return type must be specified                   put arguments in a tuple

let result: i64 = engine.call_fn(&mut scope, &ast, "hello", ( 123_i64, ) )?;
//                                                          ^^^^^^^^^^^^ tuple of one

let result: i64 = engine.call_fn(&mut scope, &ast, "hello", () )?;
//                                                          ^^ unit = tuple of zero

When using `Engine::call_fn`, the [`AST`] is always evaluated _before_ the [function] is called.

This is usually desirable in order to [import][`import`] the necessary external [modules] that are
needed by the [function].

All new [variables]/[constants] introduced are, by default, _not_ retained inside the [`Scope`].
In other words, the [`Scope`] is _rewound_ before each call.

If these default behaviors are not desirable, use `Engine::call_fn_raw`.

FuncArgs Trait


Rhai implements [`FuncArgs`][traits] for tuples and `Vec<T>`.

Engine::call_fn takes a parameter of any type that implements the FuncArgs trait, which is used to parse a data type into individual argument values for the function call.

Custom types (e.g. structures) can also implement FuncArgs so they can be used for calling Engine::call_fn.

use std::iter::once;
use rhai::FuncArgs;

// A struct containing function arguments
struct Options {
    pub foo: bool,
    pub bar: String,
    pub baz: i64
}

impl FuncArgs for Options {
    fn parse<C: Extend<Dynamic>>(self, container: &mut C) {
        container.extend(once(self.foo.into()));
        container.extend(once(self.bar.into()));
        container.extend(once(self.baz.into()));
    }
}

let options = Options { foo: true, bar: "world", baz: 42 };

// The type 'Options' can now be used as arguments to 'call_fn'!
let result: i64 = engine.call_fn(&mut scope, &ast, "hello", options)?;

Low-Level API – Engine::call_fn_raw

For more control, construct all arguments as Dynamic values and use Engine::call_fn_raw, passing it anything that implements AsMut<[Dynamic]> (such as a simple array or a Vec<Dynamic>):

let result = engine.call_fn_raw(
                &mut scope,         // scope to use
                &ast,               // AST containing the functions
                false,              // false = do not evaluate the AST
                false,              // false = do not rewind the scope (i.e. keep new variables)
                "hello",            // function entry-point
                None,               // 'this' pointer, if any
                [ "abc".into(), 123_i64.into() ]    // arguments
             )?;

Engine::call_fn_raw extends control to the following:

  • Whether to skip evaluation of the AST before calling the target function
  • Whether to rewind the custom Scope at the end of the function call
  • Whether to bind the this pointer to a specific value

Skip evaluation of the AST

By default, the AST is evaluated before calling the target function. A parameter can be passed to skip this evaluation.

Keep new variables/constants

By default, the Engine rewinds the custom Scope after each call to the initial size, so any new variable/constant defined are cleared and will not spill into the custom Scope.

This keeps the Scope from being continuously polluted by new variables and is usually the expected intuitive behavior.

A parameter can be passed to keep new variables/constants within the custom Scope. This allows the function to easily pass values back to the caller by leaving them inside the custom Scope.


If the [`Scope`] is not rewound, beware that all [variables]/[constants] defined at top level of the
[function] or in the script body will _persist_ inside the custom [`Scope`].

If any of them are temporary and not intended to be retained, define them inside a statements block
(see example below).
┌─────────────┐
│ Rhai script │
└─────────────┘

fn initialize() {
    let x = 42;                     // 'x' is retained
    let y = x * 2;                  // 'y' is retained

    // Use a new statements block to define temp variables
    {
        let temp = x + y;           // 'temp' is NOT retained

        foo = temp * temp;          // 'foo' is visible in the scope
    }
}

let foo = 123;                      // 'foo' is retained

// Use a new statements block to define temp variables
{
    let bar = foo / 2;              // 'bar' is NOT retained

    foo = bar * bar;
}


┌──────┐
│ Rust │
└──────┘

engine.call_fn_raw(&mut scope, &ast, true, false, "initialize", None, [])?;
//                                   ^^^^ evaluate AST before call
//                                         ^^^^^ do not rewind scope

// At this point, 'scope' contains these variables: 'foo', 'x', 'y'

Bind the this pointer


`Engine::call_fn` cannot call functions in _method-call_ style.

Engine::call_fn_raw can also bind a value to the this pointer of a script-defined function.

It is possible, then, to call a function that uses this.

let ast = engine.compile("fn action(x) { this += x; }")?;

let mut value: Dynamic = 1_i64.into();

engine.call_fn_raw(
            &mut scope,
            &ast,
            false,
            false,
            "action",
            Some(&mut value),       // binding the 'this' pointer
            [ 41_i64.into() ]
       )?;

assert_eq!(value.as_int()?, 42);