sal/aiprompts/rhaiwrapping_advanced.md
2025-04-04 15:05:48 +02:00

3.7 KiB

Error Handling in Dynamic Functions

When working with the dynamic function signature, error handling is slightly different:

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:

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:

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:

// 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", ())?;