rhai/doc/src/rust/register-raw.md

186 lines
9.4 KiB
Markdown
Raw Normal View History

2020-07-06 06:06:57 +02:00
Use the Low-Level API to Register a Rust Function
================================================
{{#include ../links.md}}
When a native Rust function is registered with an `Engine` using the `Engine::register_XXX` API,
Rhai transparently converts all function arguments from [`Dynamic`] into the correct types before
calling the function.
For more power and flexibility, there is a _low-level_ API to work directly with [`Dynamic`] values
without the conversions.
Raw Function Registration
-------------------------
The `Engine::register_raw_fn` method is marked _volatile_, meaning that it may be changed without warning.
If this is acceptable, then using this method to register a Rust function opens up more opportunities.
In particular, a the current _native call context_ (in form of the `NativeCallContext` type) is passed as an argument.
`NativeCallContext` exposes the current [`Engine`], among others, so the Rust function can also use [`Engine`] facilities
(such as evaluating a script).
2020-07-06 06:06:57 +02:00
```rust
engine.register_raw_fn(
"increment_by", // function name
&[ // a slice containing parameter types
std::any::TypeId::of::<i64>(), // type of first parameter
std::any::TypeId::of::<i64>() // type of second parameter
],
|context, args| { // fixed function signature
2020-07-06 06:06:57 +02:00
// Arguments are guaranteed to be correct in number and of the correct types.
// But remember this is Rust, so you can keep only one mutable reference at any one time!
// Therefore, get a '&mut' reference to the first argument _last_.
// Alternatively, use `args.split_first_mut()` etc. to split the slice first.
2020-07-06 06:06:57 +02:00
2020-09-30 17:02:01 +02:00
let y = *args[1].read_lock::<i64>().unwrap(); // get a reference to the second argument
// then copy it because it is a primary type
2020-07-06 06:06:57 +02:00
2020-09-30 17:02:01 +02:00
let y = std::mem::take(args[1]).cast::<i64>(); // alternatively, directly 'consume' it
2020-07-06 06:06:57 +02:00
2020-09-30 17:02:01 +02:00
let x = args[0].write_lock::<i64>().unwrap(); // get a '&mut' reference to the first argument
2020-07-06 06:06:57 +02:00
*x += y; // perform the action
Ok(().into()) // must be 'Result<Dynamic, Box<EvalAltResult>>'
}
);
// The above is the same as (in fact, internally they are equivalent):
2020-09-30 17:02:01 +02:00
engine.register_fn("increment_by", |x: &mut i64, y: i64| *x += y);
2020-07-06 06:06:57 +02:00
```
2020-07-26 12:17:55 +02:00
Function Signature
------------------
2020-07-06 06:06:57 +02:00
2020-07-26 12:17:55 +02:00
The function signature passed to `Engine::register_raw_fn` takes the following form:
2020-07-06 06:06:57 +02:00
2020-10-18 16:36:58 +02:00
> `Fn(context: NativeCallContext, args: &mut [&mut Dynamic])`
> `-> Result<T, Box<EvalAltResult>> + 'static`
2020-07-06 06:06:57 +02:00
where:
2020-10-27 02:56:37 +01:00
| Parameter | Type | Description |
| ----------------------------- | :-----------------------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `T` | `impl Clone` | return type of the function |
| `context` | `NativeCallContext` | the current _native call context_ |
| - `context.engine()` | `&Engine` | the current [`Engine`], with all configurations and settings.<br/>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]. |
| - `context.iter_namespaces()` | `impl Iterator<Item = &Module>` | iterator of the namespaces (as [modules]) containing all script-defined functions |
| `args` | `&mut [&mut Dynamic]` | a slice containing `&mut` references to [`Dynamic`] values.<br/>The slice is guaranteed to contain enough arguments _of the correct types_. |
2020-07-06 06:06:57 +02:00
2020-10-27 02:56:37 +01:00
### Return value
2020-07-06 06:06:57 +02:00
2020-10-27 02:56:37 +01:00
The return value is the result of the function call.
2020-10-25 14:57:18 +01:00
2020-07-06 06:06:57 +02:00
Remember, in Rhai, all arguments _except_ the _first_ one are always passed by _value_ (i.e. cloned).
Therefore, it is unnecessary to ever mutate any argument except the first one, as all mutations
will be on the cloned copy.
Extract Arguments
-----------------
To extract an argument from the `args` parameter (`&mut [&mut Dynamic]`), use the following:
2020-09-28 16:14:19 +02:00
| Argument type | Access (`n` = argument position) | Result |
| ------------------------------ | ------------------------------------- | ----------------------------------------------------- |
| [Primary type][standard types] | `args[n].clone().cast::<T>()` | copy of value |
| [Custom type] | `args[n].read_lock::<T>().unwrap()` | immutable reference to value |
| [Custom type] (consumed) | `std::mem::take(args[n]).cast::<T>()` | the _consumed_ value; the original value becomes `()` |
| `this` object | `args[0].write_lock::<T>().unwrap()` | mutable reference to value |
2020-07-06 06:06:57 +02:00
When there is a mutable reference to the `this` object (i.e. the first argument),
there can be no other immutable references to `args`, otherwise the Rust borrow checker will complain.
2020-08-06 15:10:41 +02:00
Example - Passing a Callback to a Rust Function
----------------------------------------------
2020-07-08 03:48:25 +02:00
2020-08-06 15:10:41 +02:00
The low-level API is useful when there is a need to interact with the scripting [`Engine`]
within a function.
2020-07-26 12:17:55 +02:00
The following example registers a function that takes a [function pointer] as an argument,
then calls it within the same [`Engine`]. This way, a _callback_ function can be provided
to a native Rust function.
2020-07-08 03:48:25 +02:00
```rust
use rhai::{Engine, FnPtr};
2020-07-08 03:48:25 +02:00
let mut engine = Engine::new();
// Register a Rust function
engine.register_raw_fn(
"bar",
&[
2020-10-27 02:56:37 +01:00
std::any::TypeId::of::<i64>(), // parameter types
2020-07-08 03:48:25 +02:00
std::any::TypeId::of::<FnPtr>(),
std::any::TypeId::of::<i64>(),
],
|context, args| {
2020-07-08 03:48:25 +02:00
// 'args' is guaranteed to contain enough arguments of the correct types
2020-10-27 02:56:37 +01:00
let fp = std::mem::take(args[1]).cast::<FnPtr>(); // 2nd argument - function pointer
let value = args[2].clone(); // 3rd argument - function argument
let this_ptr = args.get_mut(0).unwrap(); // 1st argument - this pointer
2020-07-08 03:48:25 +02:00
2020-07-26 12:17:55 +02:00
// Use 'FnPtr::call_dynamic' to call the function pointer.
// Beware, private script-defined functions will not be found.
fp.call_dynamic(context, Some(this_ptr), [value])
2020-07-08 03:48:25 +02:00
},
);
2020-10-27 02:56:37 +01:00
let result = engine.eval::<i64>(
r#"
2020-07-08 03:48:25 +02:00
fn foo(x) { this += x; } // script-defined function 'foo'
let x = 41; // object
x.bar(Fn("foo"), 1); // pass 'foo' as function pointer
x
2020-10-27 02:56:37 +01:00
"#)?;
2020-07-08 03:48:25 +02:00
```
2020-08-06 15:10:41 +02:00
TL;DR - Why `read_lock` and `write_lock`
---------------------------------------
The `Dynamic` API that casts it to a reference to a particular data type is `read_lock`
(for an immutable reference) and `write_lock` (for a mutable reference).
As the naming shows, something is _locked_ in order to allow this access, and that something
is a _shared value_ created by [capturing][automatic currying] variables from [closures].
Shared values are implemented as `Rc<RefCell<Dynamic>>` (`Arc<RwLock<Dynamic>>` under [`sync`]).
If the value is _not_ a shared value, or if running under [`no_closure`] where there is
2020-09-30 17:02:01 +02:00
no [capturing][automatic currying], this API de-sugars to a simple `Dynamic::downcast_ref` and
2020-10-27 02:56:37 +01:00
`Dynamic::downcast_mut`. In other words, there is no locking and reference counting overhead
for the vast majority of non-shared values.
2020-08-06 15:10:41 +02:00
If the value is a shared value, then it is first locked and the returned lock guard
then allows access to the underlying value in the specified type.
2020-07-06 06:06:57 +02:00
Hold Multiple References
------------------------
In order to access a value argument that is expensive to clone _while_ holding a mutable reference
to the first argument, either _consume_ that argument via `mem::take` as above, or use `args.split_first`
2020-07-06 06:06:57 +02:00
to partition the slice:
```rust
// Partition the slice
let (first, rest) = args.split_first_mut().unwrap();
2020-07-06 06:06:57 +02:00
// Mutable reference to the first parameter
2020-09-30 17:02:01 +02:00
let this_ptr = &mut *first.write_lock::<A>().unwrap();
2020-07-06 06:06:57 +02:00
// Immutable reference to the second value parameter
// This can be mutable but there is no point because the parameter is passed by value
2020-09-30 17:02:01 +02:00
let value_ref = &*rest[0].read_lock::<B>().unwrap();
2020-07-06 06:06:57 +02:00
```