SoFunction
Updated on 2025-04-09

Rust dynamically calls the Rhai function definition method

Rust dynamically calls the Rhai function defined by strings

When using the Rhai script engine in Rust, you can dynamically call the Rhai function represented by the incoming string.

Rhai is an embedded scripting language designed for embedding into Rust applications.

This is a basic example

Shows how to call Rhai function passed in with a string in Rust.

First, make sure you have added Rhai to yourIn the file:

[dependencies]
rhai = "0.19"  # Please check the latest version number

You can then use the following code to call the Rhai function passed in with the string:

use rhai::{Engine, EvalAltResult, FnPtr, Module, Scope};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create an Rhai engine instance    let mut engine = Engine::new();

    // Define a Rhai module with some functions    let mut module = Module::new();
    module.insert_fn("greet", |name: String| format!("Hello, {}", name));
    module.insert_fn("add", |a: i32, b: i32| a + b);

    // Register the module into the engine    engine.register_module(module)?;

    // Create a scope    let mut scope = Scope::new();

    // Example: The function name and its parameters to be called    let function_name = "greet".to_string();
    let args: Vec<Box<dyn FnPtr>> = vec![Box::new(|_| "World".to_string()) as Box<dyn FnPtr>];

    // Call function    let result: EvalAltResult = engine.eval_expression_with_scope(
        &format!("({})", function_name),
        &mut scope,
        ().cloned().collect::<Vec<_>>(),
    )?;

    // Print the result    match result {
        EvalAltResult::Value(value) => println!("Result: {}", ()?),
        _ => println!("Result is not a value"),
    }

    Ok(())
}

However, the above code has some limitations and simplifications:

  1. Parameter pass: In the above example, parameter passing is by creating aFnPtrand pass toeval_expression_with_scopeAchieved. However, this method is quite cumbersome and only works for simple function signatures.
  2. Function name processing: Function names are embedded directly into expressions via string formatting, which means you need to make sure that the incoming function names are safe (i.e., it won't cause Rhai to execute unsafe code).

A more robust approach is to use Rhai'sFnCallfunction, but this requires more setup and error handling.

This is a more general approach, but slightly more complicated

use rhai::{Engine, EvalAltResult, Module, Scope};
use rhai::serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct CallArgs {
    func: String,
    args: Vec<String>,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create an Rhai engine instance    let mut engine = Engine::new();

    // Define a Rhai module with some functions    let mut module = Module::new();
    module.insert_fn("greet", |name: String| format!("Hello, {}", name));
    module.insert_fn("add", |a: i32, b: i32| a + b);

    // Register the module into the engine    engine.register_module(module)?;

    // Create a scope    let mut scope = Scope::new();

    // Example: The function name and its parameters to be called    let call_args = CallArgs {
        func: "greet".to_string(),
        args: vec!["Alice".to_string()],
    };

    // Convert the parameter to Rhai value    let rhai_args: rhai::Array = call_args.args.into_iter().map(|arg| rhai::Value::from(arg)).collect();

    // Define a temporary Rhai function to call the target function    let call_code = format!(
        r#"
        fn call_func(func_name: String, args: Array) -> Any {{
            let func = match func_name.as_str() {{
                "greet" => greet,
                "add" => add as fn(i32, i32) -> i32,
                _ => return "Function not found".into(),
            }};
            
            match (func, ()) {{
                (greet, 1) => greet(args[0].cast::<String>()?),
                (add, 2) => add(args[0].cast::<i32>()?, args[1].cast::<i32>()?),
                _ => return "Invalid argument count".into(),
            }}
        }}
        call_func("{}", {})
        "#,
        call_args.func, rhai_args
    );

    // Call function    let result: EvalAltResult = engine.eval_expression(&call_code, &mut scope)?;

    // Print the result    match result {
        EvalAltResult::Value(value) => println!("Result: {}", ()?),
        _ => println!("Result is not a value"),
    }

    Ok(())
}

In this more general example, we define aCallArgsThe structure stores the function name and parameters, and then builds a temporary Rhai script that calls the corresponding Rhai function based on the function name and number of parameters.

This approach provides greater flexibility, but is also more complex and requires handling more error situations.

Summarize

The above is personal experience. I hope you can give you a reference and I hope you can support me more.