Export a Rust Function to Rhai
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 , or calling a function pointer. |
fn_name() | &str | name of the function called (useful when the same Rust function is mapped to multiple Rhai-callable function names) |
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:
#![allow(unused)] fn main() { 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.
#![allow(unused)] fn main() { 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()) } }