Custom Type Property Getters and Setters

A custom type can also expose properties by registering get and/or set functions.

Properties can be accessed in a Rust-like syntax:

object . property

object . property = value ;

Property getter and setter functions are called behind the scene. They each take a &mut reference to the first parameter.

Getters and setters are disabled under the no_object feature.

Engine APIFunction signature(s)
(T: Clone = custom type,
V: Clone = data type)
Can mutate T?
register_getFn(&mut T) -> Vyes, but not advised
register_setFn(&mut T, V)yes
register_get_setgetter: Fn(&mut T) -> V
setter: Fn(&mut T, V)
yes, but not advised in getter
register_get_resultFn(&mut T) -> Result<V, Box<EvalAltResult>>yes, but not advised
register_set_resultFn(&mut T, V) -> Result<(), Box<EvalAltResult>>yes

Getters Must Be Pure

By convention, property getters are assumed to be pure, meaning that they are not supposed to mutate the custom type, although there is nothing that prevents this mutation in Rust.

Even though a property getter function also takes &mut as the first parameter, Rhai assumes that no data is changed when the function is called.

Cannot Override Object Maps

Property getters and setters are mainly intended for custom types.

Any getter or setter function registered for object maps is simply ignored because the get/set syntax will be interpreted as access to properties on the object maps.

Examples


#![allow(unused)]
fn main() {
#[derive(Debug, Clone)]
struct TestStruct {
    field: String
}

impl TestStruct {
    // Remember &mut must be used even for getters.
    fn get_field(&mut self) -> String {
        // Property getters are assumed to be PURE, meaning they are
        // not supposed to mutate any data.
        self.field.clone()
    }

    fn set_field(&mut self, new_val: &str) {
        self.field = new_val.to_string();
    }

    fn new() -> Self {
        Self { field: "hello" }
    }
}

let mut engine = Engine::new();

engine.register_type::<TestStruct>()
      .register_get_set("xyz", TestStruct::get_field, TestStruct::set_field)
      .register_fn("new_ts", TestStruct::new);

let result = engine.eval::<String>(
r#"
    let a = new_ts();
    a.xyz = "42";
    a.xyz
"#)?;

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

IMPORTANT: Rhai does NOT support normal references (i.e. &T) as parameters.

Fallback to Indexer

If the getter/setter of a particular property is not defined, but an indexer is defined on the custom type with string index, then the corresponding indexer will be called with the name of the property as the index value.

In other words, indexers act as a fallback to property getters/setters.


#![allow(unused)]
fn main() {
a.foo           // if property getter for 'foo' doesn't exist...

a["foo"]        // an indexer (if any) is tried
}

This feature makes it very easy for custom types to act as property bags (similar to an object map) which can add/remove properties at will.

Chaining Updates

It is possible to chain property accesses and/or indexing (via indexers) together to modify a particular property value at the end of the chain.

Rhai detects such modifications and updates the changed values all the way back up the chain.

In the end, the syntax works as expected by intuition, automatically and without special attention.


#![allow(unused)]
fn main() {
// Assume a deeply-nested object...
let root = get_new_container_object();

root.prop1.sub["hello"].list[0].value = 42;

// The above is equivalent to:

// First getting all the intermediate values...
let prop1_value = root.prop1;                   // via property getter
let sub_value = prop1_value.sub;                // via property getter
let sub_value_item = sub_value["hello"];        // via index getter
let list_value = sub_value_item.list;           // via property getter
let list_item = list_value[0];                  // via index getter

list_item.value = 42;       // modify property value deep down the chain

// Propagate the changes back up the chain...
list_value[0] = list_item;                      // via index setter
sub_value_item.list = list_value;               // via property setter
sub_value["hello"] = sub_value_item;            // via index setter
prop1_value.sub = sub_value;                    // via property setter
root.prop1 = prop1_value;                       // via property setter

// The below prints 42...
print(root.prop1.sub["hello"].list[0].value);
}