reorganize module
This commit is contained in:
275
_archive/rhai_engine/rhaibook/engine/call-fn.md
Normal file
275
_archive/rhai_engine/rhaibook/engine/call-fn.md
Normal file
@@ -0,0 +1,275 @@
|
||||
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);
|
||||
```
|
Reference in New Issue
Block a user