rhai/doc/src/rust/custom.md
2020-06-22 22:02:49 +08:00

4.0 KiB

Register a Custom Type and its Methods

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

Rhai works seamlessly with any complex Rust type. The type can be registered with the Engine, as below.

Support for custom types can be turned off via the [no_object] feature.

use rhai::{Engine, EvalAltResult};
use rhai::RegisterFn;

#[derive(Clone)]
struct TestStruct {
    field: i64
}

impl TestStruct {
    fn update(&mut self) {
        self.field += 41;
    }

    fn new() -> Self {
        TestStruct { field: 1 }
    }
}

let mut engine = Engine::new();

engine.register_type::<TestStruct>();

engine.register_fn("update", TestStruct::update);
engine.register_fn("new_ts", TestStruct::new);

let result = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?;

println!("result: {}", result.field);           // prints 42

Register a Custom Type

A custom type must implement Clone as this allows the [Engine] to pass by value.

Notice that the custom type needs to be registered using Engine::register_type.

#[derive(Clone)]
struct TestStruct {
    field: i64
}

impl TestStruct {
    fn update(&mut self) {              // methods take &mut as first parameter
        self.field += 41;
    }

    fn new() -> Self {
        TestStruct { field: 1 }
    }
}

let mut engine = Engine::new();

engine.register_type::<TestStruct>();

Methods on Custom Type

To use native custom types, methods and functions in Rhai scripts, simply register them using one of the Engine::register_XXX API.

Below, the update and new methods are registered using Engine::register_fn.

engine.register_fn("update", TestStruct::update);   // registers 'update(&mut TestStruct)'
engine.register_fn("new_ts", TestStruct::new);      // registers 'new()'

Note: Rhai follows the convention that methods of custom types take a &mut first parameter so that invoking methods can update the types. All other parameters in Rhai are passed by value (i.e. clones).

Use the Custom Type in Scripts

The custom type is then ready for use in scripts. Scripts can see the functions and methods registered earlier. Get the evaluation result back out just as before, this time casting to the custom type:

let result = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?;

println!("result: {}", result.field);               // prints 42

Method-Call Style vs. Function-Call Style

In fact, any function with a first argument that is a &mut reference can be used as method calls because internally they are the same thing: methods on a type is implemented as a functions taking a &mut first argument.

fn foo(ts: &mut TestStruct) -> i64 {
    ts.field
}

engine.register_fn("foo", foo);                     // register ad hoc function with correct signature

let result = engine.eval::<i64>(
    "let x = new_ts(); x.foo()"                     // 'foo' can be called like a method on 'x'
)?;

println!("result: {}", result);                     // prints 1

Under [no_object], however, the method style of function calls (i.e. calling a function as an object-method) is no longer supported.

// Below is a syntax error under 'no_object' because 'clear' cannot be called in method style.
let result = engine.eval::<()>("let x = [1, 2, 3]; x.clear()")?;

[type_of()]

[type_of()] works fine with custom types and returns the name of the type.

If Engine::register_type_with_name is used to register the custom type with a special "pretty-print" name, [type_of()] will return that name instead.

engine.register_type::<TestStruct>();
engine.register_fn("new_ts", TestStruct::new);
let x = new_ts();
print(x.type_of());                                 // prints "path::to::module::TestStruct"

engine.register_type_with_name::<TestStruct>("Hello");
engine.register_fn("new_ts", TestStruct::new);
let x = new_ts();
print(x.type_of());                                 // prints "Hello"