This repository has been archived on 2025-08-04. You can view files and clone it, but cannot push or open issues or pull requests.
rhaj/rhai_wrapper/README.md

7.4 KiB

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> 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:

[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.

  1. 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));
    
  2. No Arguments: (-> INT)

    fn get_answer() -> INT { 42 }
    // engine.register_fn("get_answer_rhai", wrap_for_rhai!(get_answer, -> INT));
    
  3. 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 returns None.

  4. 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
    
  5. Custom Structs (with rhai_macros_derive)

    Assume you have a struct Point that derives ToRhaiMap and FromRhaiMap:

    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 and Vec<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 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<CustomStruct>: 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.