Simulating Closures – Capture External Variables via Automatic Currying
Use `is_shared` to check whether a particular dynamic value is shared.
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 = Fn("anon_0001").curry(x); // shared 'x' is curried
The example below is a typical tutorial sample for many languages to illustrate the traps
that may accompany capturing external [variables](variables.md) in closures.
It prints `9`, `9`, `9`, ... `9`, `9`, not `0`, `1`, `2`, ... `8`, `9`, because there is ever only
_one_ captured [variable](variables.md), and all ten closures capture the _same_
[variable](variables.md).
```rust
let list = [];
for i in 0..10 {
list.push(|| print(i)); // the for loop variable 'i' is captured
}
list.len() == 10; // 10 closures stored in the array
list[0].type_of() == "Fn"; // make sure these are closures
for f in list {
f.call(); // all references to 'i' point to the same variable!
}
```
Data races are possible in Rhai scripts.
Avoid performing a method call on a captured shared [variable](variables.md) (which essentially
takes a mutable reference to the shared object) while using that same [variable](variables.md) 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.
```rust
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'
```