188 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			188 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| ## 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<Dynamic, Box<EvalAltResult>> {
 | |
|        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<i64, Box<EvalAltResult>> {
 | |
|        arr.iter()
 | |
|           .map(|v| v.as_int())
 | |
|           .collect::<Result<Vec<i64>, _>>()
 | |
|           .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::<i64>(&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::<i64>("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::<i64>(&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");
 | |
|              });
 | |
|    }
 | |
|    ```
 |