Custom Type Property Getters and Setters

Cannot override object maps

Property getters and setters are intended for custom types.

Any getter or setter function registered for object maps is simply ignored.

Get/set syntax on object maps is interpreted as access to properties.

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 ;

The Elvis operator can be used to short-circuit processing if the object itself is ():

// returns () if object is ()
object ?. property

// no action if object is ()
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

No support for references

Rhai does NOT support normal references (i.e. &T) as parameters. All references must be mutable (i.e. &mut T).

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.

Examples

#[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: String) {
        self.field = new_val;
    }

    fn new() -> Self {
        Self { field: "hello, world!".to_string() }
    }
}

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

Fallback to Indexer

See also

See this section for details on an indexer acting as fallback to properties.

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.

a.foo           // if property getter for 'foo' doesn't exist...

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

Tip: Implement a property bag

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.

// 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);