.. | ||
examples | ||
src | ||
tests | ||
Cargo.lock | ||
Cargo.toml | ||
README.md |
Rhai Wrapper Crate
This crate provides utilities to simplify the process of wrapping Rust functions and types for use with the Rhai scripting engine. Its primary component is the wrap_for_rhai!
macro, which generates the necessary boilerplate to make Rust functions callable from Rhai scripts, including type conversions for arguments and return values.
This crate works in conjunction with the rhai_macros_derive
crate, which provides ToRhaiMap
and FromRhaiMap
derive macros for custom Rust structs.
Features
wrap_for_rhai!
macro: Simplifies registering Rust functions with Rhai.- Support for various function signatures:
- Primitive types (
INT
,FLOAT
,bool
,String
). Option<T>
for optional return values.Vec<PrimitiveType>
(e.g.,Vec<INT>
,Vec<String>
).Vec<CustomStruct>
whereCustomStruct
implementsFromRhaiMap
(for arguments) andToRhaiMap
(for elements in return values).- Custom structs as direct arguments (if they implement
FromRhaiMap
). - Custom structs as direct return values (if they implement
ToRhaiMap
).
- Primitive types (
- Automatic conversion between Rhai's
Dynamic
type and Rust types.
Dependencies
Ensure you have rhai
and rhai_macros_derive
(if using custom structs) in your Cargo.toml
:
[dependencies]
rhai = "<version>" # e.g., "1.16.0"
rhai_macros_derive = { path = "../rhai_macros_derive" } # If in the same workspace
# This crate (rhai_wrapper) would also be a local path dependency
# rhai_wrapper = { path = "../rhai_wrapper" }
wrap_for_rhai!
Macro
The wrap_for_rhai!
macro is the core of this crate. It takes your Rust function and its type signature (in a specific format) and generates a closure that Rhai can register.
Basic Usage
use rhai_wrapper::wrap_for_rhai;
use rhai::{Engine, INT, FLOAT};
// Functions to be wrapped
fn add(a: INT, b: INT) -> INT { a + b }
fn greet(name: String) -> String { format!("Hello, {}!", name) }
fn get_pi() -> FLOAT { 3.14159 }
fn main() {
let mut engine = Engine::new();
// Registering functions using the macro
engine.register_fn("add_rhai", wrap_for_rhai!(add, INT, INT -> INT));
engine.register_fn("greet_rhai", wrap_for_rhai!(greet, String -> String));
engine.register_fn("get_pi_rhai", wrap_for_rhai!(get_pi, -> FLOAT));
let result: INT = engine.eval("add_rhai(10, 32)").unwrap();
assert_eq!(result, 42);
let message: String = engine.eval(r#"greet_rhai("Rhai")"#).unwrap();
assert_eq!(message, "Hello, Rhai!");
}
Supported Signatures & Examples
The macro uses a pattern-matching style to handle different function signatures.
-
Primitives:
(INT, INT -> INT)
,(String -> String)
,(FLOAT, bool -> String)
fn my_func(a: INT, b: String) -> bool { /* ... */ false } // engine.register_fn("my_func_rhai", wrap_for_rhai!(my_func, INT, String -> bool));
-
No Arguments:
(-> INT)
fn get_answer() -> INT { 42 } // engine.register_fn("get_answer_rhai", wrap_for_rhai!(get_answer, -> INT));
-
Option<T>
Return Type:(INT -> Option<String>)
fn maybe_get_name(id: INT) -> Option<String> { if id == 1 { Some("Alice".to_string()) } else { None } } // engine.register_fn("maybe_get_name_rhai", wrap_for_rhai!(maybe_get_name, INT -> Option<String>));
Rhai will receive
()
(unit/nothing) if the Rust function returnsNone
. -
Vec<Primitive>
Argument:(Vec<INT> -> INT)
,(Vec<String> -> String)
fn sum_numbers(numbers: Vec<INT>) -> INT { numbers.iter().sum() } // engine.register_fn("sum_rhai", wrap_for_rhai!(sum_numbers, Vec<INT> -> INT)); // Rhai script: sum_rhai([1, 2, 3]) -> 6
-
Custom Structs (with
rhai_macros_derive
)Assume you have a struct
Point
that derivesToRhaiMap
andFromRhaiMap
:use rhai_macros_derive::{ToRhaiMap, FromRhaiMap}; use rhai::CustomType; #[derive(CustomType, ToRhaiMap, FromRhaiMap, Clone, Debug, PartialEq)] struct Point { x: INT, y: INT }
-
Custom Struct Argument:
(Point -> String)
fn print_point(p: Point) -> String { format!("Point(x={}, y={})", p.x, p.y) } // engine.build_type::<Point>(); // engine.register_fn("print_point_rhai", wrap_for_rhai!(print_point, Point -> String)); // Rhai script: print_point_rhai(#{x:1, y:2})
-
Custom Struct Return:
(INT, INT -> Point)
fn make_point(x: INT, y: INT) -> Point { Point { x, y } } // engine.build_type::<Point>(); // engine.register_fn("make_point_rhai", wrap_for_rhai!(make_point, INT, INT -> Point)); // Rhai script: let p = make_point_rhai(3,4); p.x == 3
-
Vec<CustomStruct>
Argument:(Vec<Point> -> INT)
fn sum_point_coords(points: Vec<Point>) -> INT { points.iter().map(|p| p.x + p.y).sum() } // engine.build_type::<Point>(); // engine.register_fn("sum_points_rhai", wrap_for_rhai!(sum_point_coords, Vec<Point> -> INT)); // Rhai script: sum_points_rhai([#{x:1,y:2}, #{x:3,y:4}])
-
Vec<CustomStruct>
Return:(INT -> Vec<Point>)
fn generate_points(count: INT) -> Vec<Point> { (0..count).map(|i| Point { x: i, y: i*2 }).collect() } // engine.build_type::<Point>(); // engine.register_fn("gen_points_rhai", wrap_for_rhai!(generate_points, INT -> Vec<Point>)); // Rhai script: let arr = gen_points_rhai(2); arr[0].x == 0
-
Vec<CustomStruct>
Argument andVec<Primitive>
Return:(Vec<Point> -> Vec<INT>)
fn get_x_coords(points: Vec<Point>) -> Vec<INT> { points.iter().map(|p| p.x).collect() } // engine.build_type::<Point>(); // engine.register_fn("get_xs_rhai", wrap_for_rhai!(get_x_coords, Vec<Point> -> Vec<INT>));
-
How Custom Structs are Handled
- When a custom struct is an argument (
MyStructType
): The macro expects the Rhai script to pass an object map. This map is then passed toMyStructType::from_rhai_map(map)
(provided by#[derive(FromRhaiMap)]
) to convert it into a Rust struct instance. - When a custom struct is a return value: The Rust function returns an instance of
MyStructType
. The macro callsinstance.to_rhai_map()
(provided by#[derive(ToRhaiMap)]
) to convert it into arhai::Map
, which Rhai receives as an object map. - For
Vec<CustomStruct>
: Similar logic applies element-wise. Incomingrhai::Array
elements are converted usingfrom_rhai_map
; outgoingVec
elements are converted usingto_rhai_map
before being collected into arhai::Array
.
Adding New Macro Arms
The wrap_for_rhai!
macro is defined with several arms, each matching a specific function signature pattern. If you need to support a new, common signature:
- Open
rhai_wrapper/src/lib.rs
. - Add a new macro arm following the existing patterns.
- Pay attention to argument conversion (from
rhai::Dynamic
orrhai::Map
) and return value conversion (torhai::Dynamic
orrhai::Map
). - For custom types, rely on
YourType::from_rhai_map(...)
andyour_instance.to_rhai_map()
. - Consider the order of macro arms if a more specific arm needs to match before a more general one.
This crate aims to reduce boilerplate and make Rhai integration smoother for common Rust patterns.