7.7 KiB
Rhai Macros Derive Crate
This crate provides procedural macros to simplify the integration of custom Rust structs with the Rhai scripting engine, specifically for converting structs to and from rhai::Map
objects. It is intended to be used alongside the rhai_wrapper
crate.
Provided Macros
#[derive(ToRhaiMap)]
#[derive(FromRhaiMap)]
Dependencies
Make sure this crate is included in your Cargo.toml
dependencies, typically as a local path dependency if working within the rhaj
project:
[dependencies]
rhai_macros_derive = { path = "../rhai_macros_derive" }
# ... other dependencies
And rhai
itself:
[dependencies]
rhai = "<version>" # e.g., "1.16.0"
#[derive(ToRhaiMap)]
This macro automatically generates an implementation of a to_rhai_map(&self) -> rhai::Map
method for your struct. This method converts an instance of your struct into a rhai::Map
, which can then be easily used within Rhai scripts as an object map.
Usage
use rhai_macros_derive::ToRhaiMap;
use rhai::{INT, FLOAT, Map};
// Forward declaration for Point if used in Vec<Point>
// Typically Point would also derive ToRhaiMap and FromRhaiMap
#[derive(Debug, Clone, PartialEq, ToRhaiMap)] // Assuming Point also derives ToRhaiMap
struct Point {
x: INT,
y: INT,
}
impl Point { // Minimal stub for example if not fully defined elsewhere
fn to_rhai_map(&self) -> Map {
let mut map = Map::new();
map.insert("x".into(), self.x.into());
map.insert("y".into(), self.y.into());
map
}
}
#[derive(ToRhaiMap)]
struct MyStruct {
id: INT,
name: String,
is_active: bool,
score: FLOAT,
position: Point, // Nested struct
tags: Vec<String>, // Vec of primitives
history: Vec<Point>, // Vec of custom structs
}
fn main() {
let p = Point { x: 10, y: 20 };
let my_instance = MyStruct {
id: 1,
name: "Test".to_string(),
is_active: true,
score: 99.5,
position: p.clone(),
tags: vec!["alpha".to_string(), "beta".to_string()],
history: vec![Point { x: 1, y: 2 }, Point { x: 3, y: 4}],
};
let rhai_map = my_instance.to_rhai_map();
assert_eq!(rhai_map.get("id").unwrap().as_int().unwrap(), 1);
assert_eq!(rhai_map.get("name").unwrap().clone().into_string().unwrap(), "Test");
assert_eq!(rhai_map.get("is_active").unwrap().as_bool().unwrap(), true);
assert_eq!(rhai_map.get("score").unwrap().as_float().unwrap(), 99.5);
let pos_map = rhai_map.get("position").unwrap().clone().try_cast::<Map>().unwrap();
assert_eq!(pos_map.get("x").unwrap().as_int().unwrap(), 10);
let tags_array = rhai_map.get("tags").unwrap().clone().try_cast::<rhai::Array>().unwrap();
assert_eq!(tags_array.len(), 2);
assert_eq!(tags_array[0].clone().into_string().unwrap(), "alpha");
let history_array = rhai_map.get("history").unwrap().clone().try_cast::<rhai::Array>().unwrap();
assert_eq!(history_array.len(), 2);
let hist_p1_map = history_array[0].clone().try_cast::<Map>().unwrap();
assert_eq!(hist_p1_map.get("x").unwrap().as_int().unwrap(), 1);
}
How it works:
- Primitive Types: Fields like
INT
,i64
,String
,FLOAT
,f64
, andbool
are cloned and converted into theirrhai::Dynamic
equivalents. - Nested Structs: If a field is another struct (e.g.,
position: Point
), that struct must also implementto_rhai_map()
. The macro will callself.field_name.to_rhai_map()
for that field. Vec<T>
Fields:- If
T
is a primitive type (e.g.,Vec<String>
), each element is cloned and converted torhai::Dynamic
, then collected into arhai::Array
. - If
T
is a custom struct (e.g.,Vec<Point>
),item.to_rhai_map()
is called for each element, and the resultingrhai::Map
s are collected into arhai::Array
.
- If
#[derive(FromRhaiMap)]
This macro automatically generates an implementation of from_rhai_map(map: rhai::Map) -> Result<Self, String>
for your struct. This method attempts to construct an instance of your struct from a rhai::Map
.
Usage
use rhai_macros_derive::FromRhaiMap;
use rhai::{INT, FLOAT, Map, Array, Dynamic};
// Assuming Point also derives FromRhaiMap and has a from_rhai_map method
#[derive(Debug, Clone, PartialEq, FromRhaiMap)]
struct Point {
x: INT,
y: INT,
}
impl Point { // Minimal stub for example
fn from_rhai_map(mut map: Map) -> Result<Self, String> {
Ok(Point {
x: map.get("x").and_then(|d| d.as_int().ok()).ok_or("x missing")?,
y: map.get("y").and_then(|d| d.as_int().ok()).ok_or("y missing")?,
})
}
}
#[derive(FromRhaiMap, Debug, PartialEq)] // Added Debug, PartialEq for assert
struct MyStruct {
id: INT,
name: String,
is_active: bool,
score: FLOAT,
position: Point, // Nested struct
tags: Vec<String>, // Vec of primitives
history: Vec<Point>, // Vec of custom structs
}
fn main() {
let mut map = Map::new();
map.insert("id".into(), (1 as INT).into());
map.insert("name".into(), "Test".to_string().into());
map.insert("is_active".into(), true.into());
map.insert("score".into(), (99.5 as FLOAT).into());
let mut pos_map = Map::new();
pos_map.insert("x".into(), (10 as INT).into());
pos_map.insert("y".into(), (20 as INT).into());
map.insert("position".into(), pos_map.into());
let tags_array: Array = vec![Dynamic::from("alpha".to_string()), Dynamic::from("beta".to_string())];
map.insert("tags".into(), tags_array.into());
let mut hist_p1_map = Map::new();
hist_p1_map.insert("x".into(), (1 as INT).into());
hist_p1_map.insert("y".into(), (2 as INT).into());
let mut hist_p2_map = Map::new();
hist_p2_map.insert("x".into(), (3 as INT).into());
hist_p2_map.insert("y".into(), (4 as INT).into());
let history_array: Array = vec![Dynamic::from(hist_p1_map), Dynamic::from(hist_p2_map)];
map.insert("history".into(), history_array.into());
let my_instance = MyStruct::from_rhai_map(map).unwrap();
assert_eq!(my_instance.id, 1);
assert_eq!(my_instance.name, "Test");
assert_eq!(my_instance.position, Point { x: 10, y: 20 });
assert_eq!(my_instance.tags, vec!["alpha".to_string(), "beta".to_string()]);
assert_eq!(my_instance.history, vec![Point { x: 1, y: 2 }, Point { x: 3, y: 4}]);
}
How it works:
- Primitive Types: For fields like
INT
,String
, etc., the macro attempts to retrieve the value from the inputrhai::Map
by its key (field name) and convert it to the expected Rust type (e.g., usingas_int()
,into_string()
). - Nested Structs: If a field is another struct, it retrieves the corresponding value as a
rhai::Dynamic
, tries to cast it torhai::Map
, and then callsNestedStructType::from_rhai_map()
on that sub-map. Vec<T>
Fields:- Retrieves the value as
rhai::Dynamic
, casts it torhai::Array
. - If
T
is a primitive type, it iterates the array, converting eachrhai::Dynamic
element toT
. - If
T
is a custom struct, it iterates the array, casting eachrhai::Dynamic
element torhai::Map
, and then callsT::from_rhai_map()
on each sub-map.
- Retrieves the value as
- Error Handling: If a field is missing, or if a type conversion fails,
from_rhai_map
will return anErr(String)
describing the issue.
Combining with rhai::CustomType
For your structs to be fully usable as custom types within Rhai (e.g., to be registered with engine.build_type::<MyStruct>()
), they should also typically derive rhai::CustomType
, Clone
, and Debug
:
use rhai_macros_derive::{ToRhaiMap, FromRhaiMap};
use rhai::CustomType;
#[derive(CustomType, ToRhaiMap, FromRhaiMap, Clone, Debug, PartialEq)]
struct MyStruct {
// ... fields
}
This setup allows seamless conversion and manipulation of your Rust structs within Rhai scripts.