This repository has been archived on 2025-08-04. You can view files and clone it, but cannot push or open issues or pull requests.
rhaj/rhai_engine/rhaibook/ref/fn-method.md
2025-04-03 09:18:05 +02:00

5.3 KiB
Raw Blame History

this Simulating an Object Method


The only way for a script-defined [function](functions.md) to change an external value is via `this`.

Arguments passed to script-defined functions are always by value because functions are pure.

However, functions can also be called in method-call style:

object . method ( parameters ... )

When a function is called this way, the keyword this binds to the object in the method call and can be changed.

fn change() {       // note that the method does not need a parameter
    this = 42;      // 'this' binds to the object in method-call
}

let x = 500;

x.change();         // call 'change' in method-call style, 'this' binds to 'x'

x == 42;            // 'x' is changed!

change();           // <- error: 'this' is unbound

Elvis Operator

The Elvis operator can be used to short-circuit the method call when the object itself is ().

object ?. method ( parameters ... )

In the above, the method is never called if object is ().

Restrict the Type of this in Function Definitions


Methods defined this way are automatically exposed to the global namespace.

In many cases it may be desirable to implement methods for different custom types using script-defined functions.

The Problem

Doing so is brittle and requires a lot of type checking code because there can only be one function definition for the same name and arity:

// Really painful way to define a method called 'do_update' on various data types
fn do_update(x) {
    switch type_of(this) {
        "i64" => this *= x,
        "string" => this.len += x,
        "bool" if this => this *= x,
        "bool" => this *= 42,
        "MyType" => this.update(x),
        "Strange-Type#Name::with_!@#symbols" => this.update(x),
        _ => throw `I don't know how to handle ${type_of(this)}`!`
    }
}

The Solution

With a special syntax, it is possible to restrict a function to be callable only when the object pointed to by this is of a certain type:

fn type name . method ( parameters ... ) { ... }

or in quotes if the type name is not a valid identifier itself:

fn "type name string" . method ( parameters ... ) { ... }


The _type name_ specified in front of the [function](functions.md) name must match the output of
[`type_of`](type-of.md) for the required type.
`int` can be used in place of the system integer type (usually `i64` or `i32`).

`float` can be used in place of the system floating-point type (usually `f64` or `f32`).

Using these make scripts more portable.

Examples

/// This 'do_update' can only be called on objects of type 'MyType' in method style
fn MyType.do_update(x, y) {
    this.update(x * y);
}

/// This 'do_update' can only be called on objects of type 'Strange-Type#Name::with_!@#symbols'
/// (which can be specified via 'Engine::register_type_with_name') in method style
fn "Strange-Type#Name::with_!@#symbols".do_update(x, y) {
    this.update(x * y);
}

/// Define a blanket version
fn do_update(x, y) {
    this = `${this}, ${x}, ${y}`;
}

/// This 'do_update' can only be called on integers in method style
fn int.do_update(x, y) {
    this += x * y
}

let obj = create_my_type();     // 'x' is 'MyType'

obj.type_of() == "MyType";

obj.do_update(42, 123);         // ok!

let x = 42;                     // 'x' is an integer

x.type_of() == "i64";

x.do_update(42, 123);           // ok!

let x = true;                   // 'x' is a boolean

x.type_of() == "bool";

x.do_update(42, 123);           // <- this works because there is a blanket version

// Use 'is_def_fn' with three parameters to test for typed methods
is_def_fn("MyType", "do_update", 2) == true;

is_def_fn("int", "do_update", 2) == true;

Bind to this for Module Functions

The Problem

The method-call syntax is not possible for functions imported from modules.

import "my_module" as foo;

let x = 42;

x.foo::change_value(1);     // <- syntax error

The Solution

In order to call a module function as a method, it must be defined with a restriction on the type of object pointed to by this:

┌────────────────┐
 my_module.rhai 
└────────────────┘

// This is a typed method function requiring 'this' to be an integer.
// Typed methods are automatically marked global when importing this module.
fn int.change_value(offset) {
    // 'this' is guaranteed to be an integer
    this += offset;
}


┌───────────┐
 main.rhai 
└───────────┘

import "my_module";

let x = 42;

x.change_value(1);          // ok!

x == 43;