Call Rhai Functions from Rust ============================= {{#include ../links.md}} Rhai also allows working _backwards_ from the other direction – i.e. calling a Rhai-scripted [function] from Rust via `Engine::call_fn`. ```rust ┌─────────────┐ │ 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 = engine.call_fn::(&mut scope, &ast, "hello", ( "abc", 123_i64 ) )?; // ^^^ ^^^^^^^^^^^^^^^^^^ // return type must be specified put arguments in a tuple let result = engine.call_fn::(&mut scope, &ast, "hello", ( 123_i64, ) )?; // ^^^^^^^^^^^^ tuple of one let result = engine.call_fn::(&mut scope, &ast, "hello", () )?; // ^^ unit = tuple of zero ``` ~~~admonish danger.small "Warning: Functions with one parameter" Functions with only one single parameter is easy to get wrong. The proper Rust syntax is a _tuple with one item_: ```rust ( arg , ) ``` Notice the comma (`,`) after the argument. Without it, the expression is a single value `(arg)` which is the same as `arg` and not a tuple. A syntax error with very confusing error message will be generated by the Rust compiler if the comma is omitted. ~~~ ~~~admonish warning.small "Default behavior" 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, override them with `Engine::call_fn_with_options`. ~~~ `FuncArgs` Trait ---------------- ```admonish note.side Rhai implements [`FuncArgs`][traits] for tuples, arrays and `Vec`. ``` `Engine::call_fn` takes a parameter of any type that implements the [`FuncArgs`][traits] 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`][traits] so they can be used for calling `Engine::call_fn`. ```rust 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>(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 argument to 'call_fn' // to call a function with three parameters: fn hello(foo, bar, baz) let result = engine.call_fn::(&mut scope, &ast, "hello", options)?; ``` ```admonish warning.small "Warning: You don't need this" Implementing `FuncArgs` is almost never needed because Rhai works directly with any [custom type]. It is used only in niche cases where a [custom type's][custom type] fields need to be split up to pass to functions. ``` `Engine::call_fn_with_options` ------------------------------ For more control, use `Engine::call_fn_with_options`, which takes a type `CallFnOptions`: ```rust use rhai::{Engine, CallFnOptions}; let options = CallFnOptions::new() .eval_ast(false) // do not evaluate the AST .rewind_scope(false) // do not rewind the scope (i.e. keep new variables) .bind_this_ptr(&mut state); // 'this' pointer let result = engine.call_fn_with_options::( options, // options &mut scope, // scope to use &ast, // AST containing the functions "hello", // function entry-point ( "abc", 123_i64 ) // arguments )?; ``` `CallFnOptions` allows control of the following: | Field | Type | Default | Build method | Description | | -------------- | :---------------------------------: | :-----: | :-------------: | --------------------------------------------------------------------------------------------------------- | | `eval_ast` | `bool` | `true` | `eval_ast` | evaluate the [`AST`] before calling the target [function] (useful to run [`import` statements]) | | `rewind_scope` | `bool` | `true` | `rewind_scope` | rewind the custom [`Scope`] at the end of the [function] call so new local variables are removed | | `this_ptr` | [`Option<&mut Dynamic>`][`Dynamic`] | `None` | `bind_this_ptr` | bind the `this` pointer to a specific value | | `tag` | [`Option`][`Dynamic`] | `None` | `with_tag` | set the _custom state_ for this evaluation (accessed via [`NativeCallContext::tag`][`NativeCallContext`]) | ### Skip evaluation of the `AST` By default, the [`AST`] is evaluated before calling the target [function]. This is necessary to make sure that necessary [modules] imported via [`import`] statements are available. Setting `eval_ast` to `false` skips 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 prevents the [`Scope`] from being continuously polluted by new [variables] and is usually the intuitively expected behavior. Setting `rewind_scope` to `false` retains 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`]. ~~~admonish warning.small "Warning: new variables persist in `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). ~~~ ```rust ┌─────────────┐ │ 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 │ └──────┘ let options = CallFnOptions::new().rewind_scope(false); engine.call_fn_with_options(options, &mut scope, &ast, "initialize", ())?; // At this point, 'scope' contains these variables: 'foo', 'x', 'y' ``` ### Bind the `this` pointer ```admonish note.side `Engine::call_fn` cannot call functions in _method-call_ style. ``` `CallFnOptions` 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`. ```rust let ast = engine.compile("fn action(x) { this += x; }")?; let mut value: Dynamic = 1_i64.into(); let options = CallFnOptions::new() .eval_ast(false) .rewind_scope(false) .bind_this_ptr(&mut value); engine.call_fn_with_options(options, &mut scope, &ast, "action", ( 41_i64, ))?; assert_eq!(value.as_int()?, 42); ```