276 lines
9.2 KiB
Markdown
276 lines
9.2 KiB
Markdown
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::<i64>(&mut scope, &ast, "hello", ( "abc", 123_i64 ) )?;
|
|
// ^^^ ^^^^^^^^^^^^^^^^^^
|
|
// return type must be specified put arguments in a tuple
|
|
|
|
let result = engine.call_fn::<i64>(&mut scope, &ast, "hello", ( 123_i64, ) )?;
|
|
// ^^^^^^^^^^^^ tuple of one
|
|
|
|
let result = engine.call_fn::<i64>(&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<T>`.
|
|
```
|
|
|
|
`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<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 argument to 'call_fn'
|
|
// to call a function with three parameters: fn hello(foo, bar, baz)
|
|
let result = engine.call_fn::<i64>(&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::<i64>(
|
|
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>`][`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);
|
|
```
|