Simulating Closures – Capture External Variables via Automatic Currying
Since anonymous functions de-sugar to standard function definitions, they retain all the behaviors of Rhai functions, including being pure, having no access to external variables.
The anonymous function syntax, however, automatically captures variables that are not defined within the current scope, but are defined in the external scope – i.e. the scope where the anonymous function is created.
Variables that are accessible during the time the anonymous function is created can be captured, as long as they are not shadowed by local variables defined within the function’s scope.
The captured variables are automatically converted into reference-counted shared values.
Therefore, similar to closures in many languages, these captured shared values persist through reference counting, and may be read or modified even after the variables that hold them go out of scope and no longer exist.
let x = 1; // a normal variable
x.is_shared() == false;
let f = |y| x + y; // variable 'x' is auto-curried (captured) into 'f'
x.is_shared() == true; // 'x' is now a shared value!
f.call(2) == 3; // 1 + 2 == 3
x = 40; // changing 'x'...
f.call(2) == 42; // the value of 'x' is 40 because 'x' is shared
// The above de-sugars into something like this:
fn anon_0001(x, y) { x + y } // parameter 'x' is inserted
make_shared(x); // convert variable 'x' into a shared value
let f = anon_0001.curry(x); // shared 'x' is curried
Data races are possible in Rhai scripts.
Avoid performing a method call on a captured shared variable (which essentially takes a mutable reference to the shared object) while using that same variable as a parameter in the method call – this is a sure-fire way to generate a data race error.
If a shared value is used as the this
pointer in a method call to a closure function,
then the same shared value must not be captured inside that function, or a data race
will occur and the script will terminate with an error.
let x = 20;
x.is_shared() == false; // 'x' not shared, so no data races
let f = |a| this += x + a; // 'x' is captured in this closure
x.is_shared() == true; // now 'x' is shared
x.call(f, 2); // <- error: data race detected on 'x'