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");
|
|
});
|
|
}
|
|
```
|