## Best Practices and Optimization When wrapping Rust functions for use with Rhai, following these best practices will help you create efficient, maintainable, and robust code. ### Performance Considerations 1. **Minimize Cloning**: Rhai often requires cloning data, but you can minimize this overhead: ```rust // Prefer immutable references when possible fn process_data(data: &MyStruct) -> i64 { // Work with data without cloning data.value * 2 } // Use mutable references for in-place modifications fn update_data(data: &mut MyStruct) { data.value += 1; } ``` 2. **Avoid Excessive Type Conversions**: Converting between Rhai's Dynamic type and Rust types has overhead: ```rust // Inefficient - multiple conversions fn process_inefficient(ctx: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { let value = args[0].as_int()?; let result = value * 2; Ok(Dynamic::from(result)) } // More efficient - use typed parameters when possible fn process_efficient(value: i64) -> i64 { value * 2 } ``` 3. **Batch Operations**: For operations on collections, batch processing is more efficient: ```rust // Process an entire array at once rather than element by element fn sum_array(arr: Array) -> Result> { arr.iter() .map(|v| v.as_int()) .collect::, _>>() .map(|nums| nums.iter().sum()) .map_err(|_| "Array must contain only integers".into()) } ``` 4. **Compile Scripts Once**: Reuse compiled ASTs for scripts that are executed multiple times: ```rust // Compile once let ast = engine.compile(script)?; // Execute multiple times with different parameters for i in 0..10 { let result = engine.eval_ast::(&ast)?; println!("Result {}: {}", i, result); } ``` ### Thread Safety 1. **Use Sync Mode When Needed**: If you need thread safety, use the `sync` feature: ```rust // In Cargo.toml // rhai = { version = "1.x", features = ["sync"] } // This creates a thread-safe engine let engine = Engine::new(); // Now you can safely share the engine between threads std::thread::spawn(move || { let result = engine.eval::("40 + 2")?; println!("Result: {}", result); }); ``` 2. **Clone the Engine for Multiple Threads**: When not using `sync`, clone the engine for each thread: ```rust let engine = Engine::new(); let handles: Vec<_> = (0..5).map(|i| { let engine_clone = engine.clone(); std::thread::spawn(move || { let result = engine_clone.eval::(&format!("{} + 2", i * 10))?; println!("Thread {}: {}", i, result); }) }).collect(); for handle in handles { handle.join().unwrap(); } ``` ### Memory Management 1. **Control Scope Size**: Be mindful of the size of your scopes: ```rust // Create a new scope for each operation to avoid memory buildup for item in items { let mut scope = Scope::new(); scope.push("item", item); engine.eval_with_scope::<()>(&mut scope, "process(item)")?; } ``` 2. **Limit Script Complexity**: Use engine options to limit script complexity: ```rust let mut engine = Engine::new(); // Set limits to prevent scripts from consuming too many resources engine.set_max_expr_depths(64, 64) // Max expression/statement depth .set_max_function_expr_depth(64) // Max function depth .set_max_array_size(10000) // Max array size .set_max_map_size(10000) // Max map size .set_max_string_size(10000) // Max string size .set_max_call_levels(64); // Max call stack depth ``` 3. **Use Shared Values Carefully**: Shared values (via closures) have reference-counting overhead: ```rust // Avoid unnecessary capturing in closures when possible engine.register_fn("process", |x: i64| x * 2); // Instead of capturing large data structures let large_data = vec![1, 2, 3, /* ... thousands of items ... */]; engine.register_fn("process_data", move |idx: i64| { if idx >= 0 && (idx as usize) < large_data.len() { large_data[idx as usize] } else { 0 } }); // Consider registering a lookup function instead let large_data = std::sync::Arc::new(vec![1, 2, 3, /* ... thousands of items ... */]); let data_ref = large_data.clone(); engine.register_fn("lookup", move |idx: i64| { if idx >= 0 && (idx as usize) < data_ref.len() { data_ref[idx as usize] } else { 0 } }); ``` ### API Design 1. **Consistent Naming**: Use consistent naming conventions: ```rust // Good: Consistent naming pattern engine.register_fn("create_user", create_user) .register_fn("update_user", update_user) .register_fn("delete_user", delete_user); // Bad: Inconsistent naming engine.register_fn("create_user", create_user) .register_fn("user_update", update_user) .register_fn("remove", delete_user); ``` 2. **Logical Function Grouping**: Group related functions together: ```rust // Register all string-related functions together engine.register_fn("str_length", |s: &str| s.len() as i64) .register_fn("str_uppercase", |s: &str| s.to_uppercase()) .register_fn("str_lowercase", |s: &str| s.to_lowercase()); // Register all math-related functions together engine.register_fn("math_sin", |x: f64| x.sin()) .register_fn("math_cos", |x: f64| x.cos()) .register_fn("math_tan", |x: f64| x.tan()); ``` 3. **Comprehensive Documentation**: Document your API thoroughly: ```rust // Add documentation for script writers let mut engine = Engine::new(); #[cfg(feature = "metadata")] { // Add function documentation engine.register_fn("calculate_tax", calculate_tax) .register_fn_metadata("calculate_tax", |metadata| { metadata.set_doc_comment("Calculates tax based on income and rate.\n\nParameters:\n- income: Annual income\n- rate: Tax rate (0.0-1.0)\n\nReturns: Calculated tax amount"); }); } ```