..
This commit is contained in:
		
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										134
									
								
								herodb/aiprompts/rhaiwrapping_advanced.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								herodb/aiprompts/rhaiwrapping_advanced.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,134 @@
 | 
			
		||||
 | 
			
		||||
### Error Handling in Dynamic Functions
 | 
			
		||||
 | 
			
		||||
When working with the dynamic function signature, error handling is slightly different:
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
fn dynamic_function(ctx: NativeCallContext, args: &mut [&mut Dynamic]) -> Result<Dynamic, Box<EvalAltResult>> {
 | 
			
		||||
    // Get the position information from the context
 | 
			
		||||
    let pos = ctx.position();
 | 
			
		||||
    
 | 
			
		||||
    // Validate arguments
 | 
			
		||||
    if args.len() < 2 {
 | 
			
		||||
        return Err(Box::new(EvalAltResult::ErrorRuntime(
 | 
			
		||||
            format!("Expected at least 2 arguments, got {}", args.len()),
 | 
			
		||||
            pos
 | 
			
		||||
        )));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Try to convert arguments with proper error handling
 | 
			
		||||
    let arg1 = match args[0].as_int() {
 | 
			
		||||
        Ok(val) => val,
 | 
			
		||||
        Err(_) => return Err(Box::new(EvalAltResult::ErrorMismatchOutputType(
 | 
			
		||||
            "Expected first argument to be an integer".into(),
 | 
			
		||||
            pos,
 | 
			
		||||
            "i64".into()
 | 
			
		||||
        )))
 | 
			
		||||
    };
 | 
			
		||||
    
 | 
			
		||||
    // Process with error handling
 | 
			
		||||
    if arg1 <= 0 {
 | 
			
		||||
        return Err(Box::new(EvalAltResult::ErrorRuntime(
 | 
			
		||||
            "First argument must be positive".into(),
 | 
			
		||||
            pos
 | 
			
		||||
        )));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Return success
 | 
			
		||||
    Ok(Dynamic::from(arg1 * 2))
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Advanced Patterns
 | 
			
		||||
 | 
			
		||||
### Working with Function Pointers
 | 
			
		||||
 | 
			
		||||
You can create function pointers that bind to Rust functions:
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
fn my_awesome_fn(ctx: NativeCallContext, args: &mut[&mut Dynamic]) -> Result<Dynamic, Box<EvalAltResult>> {
 | 
			
		||||
    // Check number of arguments
 | 
			
		||||
    if args.len() != 2 {
 | 
			
		||||
        return Err("one argument is required, plus the object".into());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Get call arguments
 | 
			
		||||
    let x = args[1].try_cast::<i64>().map_err(|_| "argument must be an integer".into())?;
 | 
			
		||||
 | 
			
		||||
    // Get mutable reference to the object map, which is passed as the first argument
 | 
			
		||||
    let map = &mut *args[0].as_map_mut().map_err(|_| "object must be a map".into())?;
 | 
			
		||||
 | 
			
		||||
    // Do something awesome here ...
 | 
			
		||||
    let result = x * 2;
 | 
			
		||||
 | 
			
		||||
    Ok(result.into())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Register a function to create a pre-defined object
 | 
			
		||||
engine.register_fn("create_awesome_object", || {
 | 
			
		||||
    // Use an object map as base
 | 
			
		||||
    let mut map = Map::new();
 | 
			
		||||
 | 
			
		||||
    // Create a function pointer that binds to 'my_awesome_fn'
 | 
			
		||||
    let fp = FnPtr::from_fn("awesome", my_awesome_fn)?;
 | 
			
		||||
    //                      ^ name of method
 | 
			
		||||
    //                                 ^ native function
 | 
			
		||||
 | 
			
		||||
    // Store the function pointer in the object map
 | 
			
		||||
    map.insert("awesome".into(), fp.into());
 | 
			
		||||
 | 
			
		||||
    Ok(Dynamic::from_map(map))
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Creating Rust Closures from Rhai Functions
 | 
			
		||||
 | 
			
		||||
You can encapsulate a Rhai script as a Rust closure:
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
use rhai::{Engine, Func};
 | 
			
		||||
 | 
			
		||||
let engine = Engine::new();
 | 
			
		||||
 | 
			
		||||
let script = "fn calc(x, y) { x + y.len < 42 }";
 | 
			
		||||
 | 
			
		||||
// Create a Rust closure from a Rhai function
 | 
			
		||||
let func = Func::<(i64, &str), bool>::create_from_script(
 | 
			
		||||
    engine,         // the 'Engine' is consumed into the closure
 | 
			
		||||
    script,         // the script
 | 
			
		||||
    "calc"          // the entry-point function name
 | 
			
		||||
)?;
 | 
			
		||||
 | 
			
		||||
// Call the closure
 | 
			
		||||
let result = func(123, "hello")?;
 | 
			
		||||
 | 
			
		||||
// Pass it as a callback to another function
 | 
			
		||||
schedule_callback(func);
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Calling Rhai Functions from Rust
 | 
			
		||||
 | 
			
		||||
You can call Rhai functions from Rust:
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
// Compile the script to AST
 | 
			
		||||
let ast = engine.compile(script)?;
 | 
			
		||||
 | 
			
		||||
// Create a custom 'Scope'
 | 
			
		||||
let mut scope = Scope::new();
 | 
			
		||||
 | 
			
		||||
// Add variables to the scope
 | 
			
		||||
scope.push("my_var", 42_i64);
 | 
			
		||||
scope.push("my_string", "hello, world!");
 | 
			
		||||
scope.push_constant("MY_CONST", true);
 | 
			
		||||
 | 
			
		||||
// Call a function defined in the script
 | 
			
		||||
let result = engine.call_fn::<i64>(&mut scope, &ast, "hello", ("abc", 123_i64))?;
 | 
			
		||||
 | 
			
		||||
// For a function with one parameter, use a tuple with a trailing comma
 | 
			
		||||
let result = engine.call_fn::<i64>(&mut scope, &ast, "hello", (123_i64,))?;
 | 
			
		||||
 | 
			
		||||
// For a function with no parameters
 | 
			
		||||
let result = engine.call_fn::<i64>(&mut scope, &ast, "hello", ())?;
 | 
			
		||||
```
 | 
			
		||||
							
								
								
									
										187
									
								
								herodb/aiprompts/rhaiwrapping_best_practices.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								herodb/aiprompts/rhaiwrapping_best_practices.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,187 @@
 | 
			
		||||
## 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");
 | 
			
		||||
             });
 | 
			
		||||
   }
 | 
			
		||||
   ```
 | 
			
		||||
		Reference in New Issue
	
	Block a user