7.7 KiB
Export a Rust Function to Rhai
{{#include ../links.md}}
Sometimes only a few ad hoc functions are required and it is simpler to register individual functions instead of a full-blown [plugin module].
Macros
Macro | Signature | Description |
---|---|---|
#[export_fn] |
apply to rust function defined in a Rust module | exports the function |
register_exported_fn! |
register_exported_fn!(&mut engine, " name", function) |
registers the function into an [Engine ] under a specific name |
set_exported_fn! |
set_exported_fn!(&mut module, " name", function) |
registers the function into a [Module ] under a specific name |
set_exported_global_fn! |
set_exported_global_fn!(&mut module, " name", function) |
registers the function into a [Module ] under a specific name, exposing it to the global namespace |
#[export_fn]
and register_exported_fn!
Apply #[export_fn]
onto a function defined at module level to convert it into a Rhai plugin function.
The function cannot be nested inside another function – it can only be defined directly under a module.
To register the plugin function, simply call register_exported_fn!
. The name of the function can be
any text string, so it is possible to register overloaded functions as well as operators.
use rhai::plugin::*; // import macros
#[export_fn]
fn increment(num: &mut i64) {
*num += 1;
}
fn main() {
let mut engine = Engine::new();
// 'register_exported_fn!' registers the function as 'inc' with the Engine.
register_exported_fn!(engine, "inc", increment);
}
Fallible Functions
To register [fallible functions] (i.e. functions that may return errors), apply the
#[rhai_fn(return_raw)]
attribute on plugin functions that return Result<Dynamic, Box<EvalAltResult>>
.
A syntax error is generated if the function with #[rhai_fn(return_raw)]
does not
have the appropriate return type.
use rhai::plugin::*; // a "prelude" import for macros
#[export_fn]
#[rhai_fn(return_raw)]
pub fn double_and_divide(x: i64, y: i64) -> Result<Dynamic, Box<EvalAltResult>> {
if y == 0 {
Err("Division by zero!".into())
} else {
let result = (x * 2) / y;
Ok(result.into())
}
}
fn main() {
let mut engine = Engine::new();
// Overloads the operator '+' with the Engine.
register_exported_fn!(engine, "+", double_and_divide);
}
NativeCallContext
Parameter
If the first parameter of a function is of type rhai::NativeCallContext
, then it is treated
specially by the plugins system.
NativeCallContext
is a type that encapsulates the current native call context and exposes the following:
Field | Type | Description |
---|---|---|
engine() |
&Engine |
the current [Engine ], with all configurations and settings.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]. |
source() |
Option<&str> |
reference to the current source, if any |
iter_imports() |
impl Iterator<Item = (&str, &Module)> |
iterator of the current stack of [modules] imported via import statements |
imports() |
&Imports |
reference to the current stack of [modules] imported via import statements; requires the [internals ] feature |
iter_namespaces() |
impl Iterator<Item = &Module> |
iterator of the namespaces (as [modules]) containing all script-defined functions |
namespaces() |
&[&Module] |
reference to the namespaces (as [modules]) containing all script-defined functions; requires the [internals ] feature |
This first parameter, if exists, will be stripped before all other processing. It is virtual. Most importantly, it does not count as a parameter to the function and there is no need to provide this argument when calling the function in Rhai.
The native call context can be used to call a [function pointer] or [closure] that has been passed as a parameter to the function, thereby implementing a callback:
use rhai::{Dynamic, FnPtr, NativeCallContext, EvalAltResult};
use rhai::plugin::*; // a "prelude" import for macros
#[export_fn]
#[rhai_fn(return_raw)]
pub fn greet(context: NativeCallContext, callback: FnPtr)
-> Result<Dynamic, Box<EvalAltResult>>
{
// Call the callback closure with the current context
// to obtain the name to greet!
let name = callback.call_dynamic(context, None, [])?;
Ok(format!("hello, {}!", name).into())
}
The native call context is also useful in another scenario: protecting a function from malicious scripts.
use rhai::{Dynamic, Array, NativeCallContext, EvalAltResult, Position};
use rhai::plugin::*; // a "prelude" import for macros
// This function builds an array of arbitrary size, but is protected
// against attacks by first checking with the allowed limit set
// into the 'Engine'.
#[export_fn]
#[rhai_fn(return_raw)]
pub fn grow(context: NativeCallContext, size: i64)
-> Result<Dynamic, Box<EvalAltResult>>
{
// Make sure the function does not generate a
// data structure larger than the allowed limit
// for the Engine!
if size as usize > context.engine().max_array_size()
{
return EvalAltResult::ErrorDataTooLarge(
"Size to grow".to_string(),
context.engine().max_array_size(),
size as usize,
Position::NONE,
).into();
}
let array = Array::new();
for x in 0..size {
array.push(x.into());
}
OK(array.into())
}