# 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` for optional return values. - `Vec` (e.g., `Vec`, `Vec`). - `Vec` where `CustomStruct` implements `FromRhaiMap` (for arguments) and `ToRhaiMap` (for elements in return values). - Custom structs as direct arguments (if they implement `FromRhaiMap`). - Custom structs as direct return values (if they implement `ToRhaiMap`). - 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`: ```toml [dependencies] rhai = "" # 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 ```rust 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. 1. **Primitives**: `(INT, INT -> INT)`, `(String -> String)`, `(FLOAT, bool -> String)` ```rust fn my_func(a: INT, b: String) -> bool { /* ... */ false } // engine.register_fn("my_func_rhai", wrap_for_rhai!(my_func, INT, String -> bool)); ``` 2. **No Arguments**: `(-> INT)` ```rust fn get_answer() -> INT { 42 } // engine.register_fn("get_answer_rhai", wrap_for_rhai!(get_answer, -> INT)); ``` 3. **`Option` Return Type**: `(INT -> Option)` ```rust fn maybe_get_name(id: INT) -> Option { if id == 1 { Some("Alice".to_string()) } else { None } } // engine.register_fn("maybe_get_name_rhai", wrap_for_rhai!(maybe_get_name, INT -> Option)); ``` *Rhai will receive `()` (unit/nothing) if the Rust function returns `None`.* 4. **`Vec` Argument**: `(Vec -> INT)`, `(Vec -> String)` ```rust fn sum_numbers(numbers: Vec) -> INT { numbers.iter().sum() } // engine.register_fn("sum_rhai", wrap_for_rhai!(sum_numbers, Vec -> INT)); // Rhai script: sum_rhai([1, 2, 3]) -> 6 ``` 5. **Custom Structs (with `rhai_macros_derive`)** Assume you have a struct `Point` that derives `ToRhaiMap` and `FromRhaiMap`: ```rust 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)` ```rust fn print_point(p: Point) -> String { format!("Point(x={}, y={})", p.x, p.y) } // engine.build_type::(); // 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)` ```rust fn make_point(x: INT, y: INT) -> Point { Point { x, y } } // engine.build_type::(); // 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` Argument**: `(Vec -> INT)` ```rust fn sum_point_coords(points: Vec) -> INT { points.iter().map(|p| p.x + p.y).sum() } // engine.build_type::(); // engine.register_fn("sum_points_rhai", wrap_for_rhai!(sum_point_coords, Vec -> INT)); // Rhai script: sum_points_rhai([#{x:1,y:2}, #{x:3,y:4}]) ``` - **`Vec` Return**: `(INT -> Vec)` ```rust fn generate_points(count: INT) -> Vec { (0..count).map(|i| Point { x: i, y: i*2 }).collect() } // engine.build_type::(); // engine.register_fn("gen_points_rhai", wrap_for_rhai!(generate_points, INT -> Vec)); // Rhai script: let arr = gen_points_rhai(2); arr[0].x == 0 ``` - **`Vec` Argument and `Vec` Return**: `(Vec -> Vec)` ```rust fn get_x_coords(points: Vec) -> Vec { points.iter().map(|p| p.x).collect() } // engine.build_type::(); // engine.register_fn("get_xs_rhai", wrap_for_rhai!(get_x_coords, Vec -> Vec)); ``` ### 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 to `MyStructType::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 calls `instance.to_rhai_map()` (provided by `#[derive(ToRhaiMap)]`) to convert it into a `rhai::Map`, which Rhai receives as an object map. - For `Vec`: Similar logic applies element-wise. Incoming `rhai::Array` elements are converted using `from_rhai_map`; outgoing `Vec` elements are converted using `to_rhai_map` before being collected into a `rhai::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: 1. Open `rhai_wrapper/src/lib.rs`. 2. Add a new macro arm following the existing patterns. 3. Pay attention to argument conversion (from `rhai::Dynamic` or `rhai::Map`) and return value conversion (to `rhai::Dynamic` or `rhai::Map`). 4. For custom types, rely on `YourType::from_rhai_map(...)` and `your_instance.to_rhai_map()`. 5. 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.