Call a Function Within the Caller’s Scope

Peeking Out of The Pure Box

Rhai functions are pure, meaning that they depend on on their arguments and have no access to the calling environment.

When a function accesses a variable that is not defined within that function’s scope, it raises an evaluation error.

It is possible, through a special syntax, to actually run the function call within the scope of the parent caller – i.e. the scope that makes the function call – and access/mutate variables defined there.

Obviously, this is only meaningful for scripted functions. Native Rust functions can never access any scope anyway.


#![allow(unused)]
fn main() {
fn foo(y) {             // function accesses 'x' and 'y', but 'x' is not defined
    x += y;             // 'x' is modified in this function
    let z = 0;          // 'z' is defined in this function's scope
    x
}

let x = 1;              // 'x' is defined here in the parent scope

foo(41);                // error: variable 'x' not found

// Calling a function with a '!' causes it to run within the caller's scope

foo!(41) == 42;         // the function can access and mutate the value of 'x'!

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

z == 0;                 // <- error: variable 'z' not found

x.method!();            // <- syntax error: not allowed in method-call style

// Also works for function pointers

let f = Fn("foo");

call!(f, 42) == 84;     // must use function-call style

x == 84;                // 'x' is changed once again

f.call!(41);            // <- syntax error: not allowed in method-call style

// But not available for module functions

import "hello" as h;

h::greet!();            // <- syntax error: not allowed in namespace-qualified calls
}

The Caller’s Scope Can be Mutated

Changes to variables in the calling scope persist.

Therefore, with this syntax, it is possible for a Rhai function to mutate its calling environment.

Caveat Emptor

Functions relying on the calling scope is often a Very Bad Idea™ because it makes code almost impossible to reason about and maintain, as their behaviors are volatile and unpredictable.

Rhai functions are normally pure, meaning that you can rely on the fact that they never mutate the outside environment. Using this syntax breaks this guarantee.

Functions called in this manner behave more like macros that are expanded inline than actual function calls, thus the syntax is also similar to Rust’s macro invocations.

This usage should be at the last resort. YOU HAVE BEEN WARNED.