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/language/fn-method.md
2025-04-03 09:18:05 +02:00

5.2 KiB
Raw Blame History

this Simulating an Object Method

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


The only way for a script-defined [function] 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 (not available under [no_object]):

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][elvis] 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][function namespaces].

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 ... ) { ... }

Needless to say, this typed method definition style is not available under [no_object].


The _type name_ specified in front of the [function] name must match the output of [`type_of`]
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][import] 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;