Custom Type Indexers

A custom type can also expose an indexer by registering an indexer function.

A custom type with an indexer function defined can use the bracket notation to get/set a property value at a particular index:

object [ index ]

object [ index ] = value ;

Like property getters/setters, indexers take a &mut reference to the first parameter.

They also take an additional parameter of any type that serves as the index within brackets.

Indexers are disabled when the no_index feature is used.

Engine APIFunction signature(s)
(T: Clone = custom type,
X: Clone = index type,
V: Clone = data type)
Can mutate T?
register_indexer_getFn(&mut T, X) -> Vyes, but not advised
register_indexer_setFn(&mut T, X, V)yes
register_indexer_get_setgetter: Fn(&mut T, X) -> V
setter: Fn(&mut T, X, V)
yes, but not advised in getter
register_indexer_get_resultFn(&mut T, X) -> Result<Dynamic, Box<EvalAltResult>>yes, but not advised
register_indexer_set_resultFn(&mut T, X, V) -> Result<(), Box<EvalAltResult>>yes

By convention, index getters are not supposed to mutate the custom type, although there is nothing that prevents this mutation.

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

Cannot Override Arrays, Object Maps and Strings

For efficiency reasons, indexers cannot be used to overload (i.e. override) built-in indexing operations for arrays, object maps and strings.

Attempting to register indexers for an array, object map or string panics.

Negative Integer Index

If the indexer takes a signed integer as an index (e.g. the standard INT type), care should be taken to handle negative values passed as the index.

It is a standard API style for Rhai to assume that an index position counts backwards from the end if it is negative.

-1 as an index usually refers to the last item, -2 the second to last item, and so on.

Therefore, negative index values go from -1 (last item) to -length (first item).

A typical implementation for negative index values is:

fn main() {
// The following assumes:
//   'index' is 'INT', 'items_len: usize' is the number of elements
let actual_index: usize = if index < 0 {
    index.checked_abs().map_or(0, |n| items_len - (n as usize).min(items_len))
} else {
    index as usize


fn main() {
#[derive(Debug, Clone)]
struct TestStruct {
    fields: Vec<i64>

impl TestStruct {
    // Remember &mut must be used even for getters
    fn get_field(&mut self, index: String) -> i64 {
    fn set_field(&mut self, index: String, value: i64) {
        self.fields[index.len()] = value

    fn new() -> Self {
        Self { fields: vec![1, 2, 3, 4, 5] }

let mut engine = Engine::new();

      .register_fn("new_ts", TestStruct::new)
      // Short-hand: .register_indexer_get_set(TestStruct::get_field, TestStruct::set_field);

let result = engine.eval::<i64>(r#"
                let a = new_ts();
                a["xyz"] = 42;          // these indexers use strings
                a["xyz"]                // as the index type

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

Indexer as Property Access Fallback

An indexer taking a string index is a special case. It acts as a fallback to property getters/setters.

During a property access, if the appropriate property getter/setter is not defined, an indexer is called and passed the string name of the property.

The reverse, however, is not true – when an indexer fails or doesn’t exist, the corresponding property getter/setter, if any, is not called.

fn main() {
type MyType = HashMap<String, i64>;

let mut engine = Engine::new();

// Define custom type, property getter and string indexers
      .register_fn("new_ts", || {
          let mut object = MyType::new();
          object.insert("foo", 1);
          object.insert("bar", 42);
          object.insert("baz", 123);
      // Property 'hello'
      .register_get("hello", |object: &mut MyType| object.len() as i64)
      // Index getter/setter
      .register_indexer_get(|object: &mut MyType, index: &str| *object[index])
      .register_indexer_set(|object: &mut MyType, index: &str, value: i64| object[index] = value);

// Calls a["foo"] because getter for 'foo' does not exist
engine.consume("let a = new_ts(); print(;");

// Calls a["bar"] because getter for 'bar' does not exist
engine.consume("let a = new_ts(); print(;");

// Calls a["baz"] = 999 because getter for 'baz' does not exist
engine.consume("let a = new_ts(); a.baz = 999;");

// Error: Property getter is not a fallback for indexer
engine.consume(r#"let a = new_ts(); print(a["hello"]);"#);