rhai/doc/src/rust/register-raw.md
2020-07-27 12:52:32 +08:00

6.9 KiB

Use the Low-Level API to Register a Rust Function

{{#include ../links.md}}

When a native Rust function is registered with an Engine using the Engine::register_XXX API, Rhai transparently converts all function arguments from [Dynamic] into the correct types before calling the function.

For more power and flexibility, there is a low-level API to work directly with [Dynamic] values without the conversions.

Raw Function Registration

The Engine::register_raw_fn method is marked volatile, meaning that it may be changed without warning.

If this is acceptable, then using this method to register a Rust function opens up more opportunities.

In particular, a reference to the current Engine instance is passed as an argument so the Rust function can also use Engine facilities (like evaluating a script).

engine.register_raw_fn(
    "increment_by",                                         // function name
    &[                                                      // a slice containing parameter types
        std::any::TypeId::of::<i64>(),                      // type of first parameter
        std::any::TypeId::of::<i64>()                       // type of second parameter
    ],
    |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| {    // fixed function signature
        // Arguments are guaranteed to be correct in number and of the correct types.

        // But remember this is Rust, so you can keep only one mutable reference at any one time!
        // Therefore, get a '&mut' reference to the first argument _last_.
        // Alternatively, use `args.split_at_mut(1)` etc. to split the slice first.

        let y: i64 = *args[1].downcast_ref::<i64>()         // get a reference to the second argument
                             .unwrap();                     // then copying it because it is a primary type

        let y: i64 = std::mem::take(args[1]).cast::<i64>(); // alternatively, directly 'consume' it

        let x: &mut i64 = args[0].downcast_mut::<i64>()     // get a '&mut' reference to the
                                 .unwrap();                 // first argument

        *x += y;                                            // perform the action

        Ok(().into())                                       // must be 'Result<Dynamic, Box<EvalAltResult>>'
    }
);

// The above is the same as (in fact, internally they are equivalent):

engine.register_fn("increment_by", |x: &mut i64, y: i64| x += y);

Function Signature

The function signature passed to Engine::register_raw_fn takes the following form:

Fn(engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]) -> Result<T, Box<EvalAltResult>> + 'static

where:

  • T : Variant + Clone - return type of the function.

  • engine : &Engine - the current [Engine], with all configurations and settings.

  • lib : &Module - the current global library of script-defined functions, as a [Module]. This is sometimes useful for calling a script-defined function within the same evaluation context using [Engine::call_fn][call_fn].

  • args : &mut [&mut Dynamic] - a slice containing &mut references to [Dynamic] values. The slice is guaranteed to contain enough arguments of the correct types.

Remember, in Rhai, all arguments except the first one are always passed by value (i.e. cloned). Therefore, it is unnecessary to ever mutate any argument except the first one, as all mutations will be on the cloned copy.

Extract Arguments

To extract an argument from the args parameter (&mut [&mut Dynamic]), use the following:

Argument type Access (n = argument position) Result
[Primary type][standard types] args[n].clone().cast::<T>() Copy of value.
Custom type args[n].downcast_ref::<T>().unwrap() Immutable reference to value.
Custom type (consumed) std::mem::take(args[n]).cast::<T>() The consumed value.
The original value becomes ().
this object args[0].downcast_mut::<T>().unwrap() Mutable reference to value.

When there is a mutable reference to the this object (i.e. the first argument), there can be no other immutable references to args, otherwise the Rust borrow checker will complain.

Example - Passing a Function Pointer to a Rust Function

The low-level API is useful when there is a need to interact with the scripting [Engine] within a function.

The following example registers a function that takes a [function pointer] as an argument, then calls it within the same [Engine]. This way, a callback function can be provided to a native Rust function.

use rhai::{Engine, Module, Dynamic, FnPtr};

let mut engine = Engine::new();

// Register a Rust function
engine.register_raw_fn(
    "bar",
    &[
        std::any::TypeId::of::<i64>(),                          // parameter types
        std::any::TypeId::of::<FnPtr>(),
        std::any::TypeId::of::<i64>(),
    ],
    move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| {
        // 'args' is guaranteed to contain enough arguments of the correct types

        let fp = std::mem::take(args[1]).cast::<FnPtr>();       // 2nd argument - function pointer
        let value = args[2].clone();                            // 3rd argument - function argument
        let this_ptr = args.get_mut(0).unwrap();                // 1st argument - this pointer

        // Use 'FnPtr::call_dynamic' to call the function pointer.
        // Beware, private script-defined functions will not be found.
        fp.call_dynamic(engine, lib, Some(this_ptr), [value])
    },
);

let result = engine.eval::<i64>(r#"
                fn foo(x) { this += x; }    // script-defined function 'foo'

                let x = 41;                 // object
                x.bar(Fn("foo"), 1);        // pass 'foo' as function pointer
                x
"#)?;

Hold Multiple References

In order to access a value argument that is expensive to clone while holding a mutable reference to the first argument, either consume that argument via mem::take as above, or use args.split_at to partition the slice:

// Partition the slice
let (first, rest) = args.split_at_mut(1);

// Mutable reference to the first parameter
let this_ptr = first[0].downcast_mut::<A>().unwrap();

// Immutable reference to the second value parameter
// This can be mutable but there is no point because the parameter is passed by value
let value = rest[0].downcast_ref::<B>().unwrap();