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/engine/scope.md
2025-04-19 08:10:30 +02:00

11 KiB
Raw Blame History

Scope Maintaining State

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

By default, Rhai treats each [Engine] invocation as a fresh one, persisting only the functions that have been registered but no global state.

This gives each evaluation a clean starting slate.

In order to continue using the same global state from one invocation to the next, such a state (a Scope) must be manually created and passed in.

All Scope [variables] and [constants] have values that are [Dynamic], meaning they can store values of any type.

Under [sync], however, only types that are Send + Sync are supported, and the entire Scope itself will also be Send + Sync. This is extremely useful in multi-threaded applications.


A newly-added [variable] or [constant] _[shadows][shadow]_ previous ones of the same name.

In other words, all versions are kept for [variables] and [constants], but only the latest ones can
be accessed via `get_value<T>`, `get_mut<T>` and `set_value<T>`.

Essentially, a `Scope` is always searched in _reverse order_.

`Scope` has a _lifetime_ parameter, in the vast majority of cases it can be omitted and
automatically inferred to be `'static`.

Currently, that lifetime parameter is not used.  It is there to maintain backwards compatibility
as well as for possible future expansion when references can also be put into the `Scope`.

The lifetime parameter is not guaranteed to remain unused for future versions.

In order to put a `Scope` into a `struct`, use `Scope<'static>`.

`Scope` also has a _`const` generic_ parameter, which is a number that defaults to 8.
It indicates the number of entries that the `Scope` can keep _inline_ without allocations.

The larger this number, the larger the `Scope` type gets, but allocations will happen far
less frequently.

A smaller number makes `Scope` smaller, but allocation costs will be incurred when the
number of entries exceed the _inline_ capacity.

Scope API

Method Description
new instance method create a new empty Scope
with_capacity instance method create a new empty Scope with a specified initial capacity
len number of [variables]/[constants] currently within the Scope
rewind rewind (i.e. reset) the Scope to a particular number of [variables]/[constants]
clear remove all [variables]/[constants] from the Scope, making it empty
is_empty is the Scope empty?
is_constant is the particular [variable]/[constant] in the Scope a [constant]?
push, push_constant add a new [variable]/[constant] into the Scope with a specified value
push_dynamic, push_constant_dynamic add a new [variable]/[constant] into the Scope with a [Dynamic] value
set_or_push<T> set the value of the last [variable] within the Scope by name if it exists and is not [constant]; add a new [variable] into the Scope otherwise
contains does the particular [variable] or [constant] exist in the Scope?
get_value<T> get the value of the last [variable]/[constant] within the Scope by name
set_value<T> set the value of the last [variable] within the Scope by name, panics if it is [constant]
remove<T> remove the last [variable]/[constant] from the Scope by name, returning its value
get get a reference to the value of the last [variable]/[constant] within the Scope by name
get_mut get a reference to the value of the last [variable] within the Scope by name, None if it is [constant]
set_alias [exported][export] the last [variable]/[constant] within the Scope by name
iter, iter_raw, IntoIterator::into_iter get an iterator to the [variables]/[constants] within the Scope
Extend::extend add [variables]/[constants] to the Scope

For details on the `Scope` API, refer to the
[documentation](https://docs.rs/rhai/{{version}}/rhai/struct.Scope.html) online.

Serializing/Deserializing

With the [serde] feature, Scope is serializable and deserializable via serde.

[Custom types] stored in the Scope, however, are serialized as full type-name strings. Data in [custom types] are not serialized.

Example

In the following example, a Scope is created with a few initialized variables, then it is threaded through multiple evaluations.

use rhai::{Engine, Scope, EvalAltResult};

let engine = Engine::new();

// First create the state
let mut scope = Scope::new();

// Then push (i.e. add) some initialized variables into the state.
// Remember the system number types in Rhai are i64 (i32 if 'only_i32')
// and f64 (f32 if 'f32_float').
// Better stick to them or it gets hard working with the script.
scope.push("y", 42_i64)
     .push("z", 999_i64)
     .push_constant("MY_NUMBER", 123_i64)       // constants can also be added
     .set_value("s", "hello, world!");          // 'set_value' adds a new variable when one doesn't exist

// First invocation
engine.run_with_scope(&mut scope, 
"
    let x = 4 + 5 - y + z + MY_NUMBER + s.len;
    y = 1;
")?;

// Second invocation using the same state.
// Notice that the new variable 'x', defined previously, is still here.
let result = engine.eval_with_scope::<i64>(&mut scope, "x + y")?;

println!("result: {result}");                   // prints 1103

// Variable y is changed in the script - read it with 'get_value'
assert_eq!(scope.get_value::<i64>("y").expect("variable y should exist"), 1);

// We can modify scope variables directly with 'set_value'
scope.set_value("y", 42_i64);
assert_eq!(scope.get_value::<i64>("y").expect("variable y should exist"), 42);

Engine API Using Scope

[Engine] API methods that accept a Scope parameter all end in _with_scope, making that Scope (and everything inside it) available to the script:

Engine API Not available under
Engine::eval_with_scope
Engine::eval_ast_with_scope
Engine::eval_file_with_scope [no_std]
Engine::eval_expression_with_scope
Engine::run_with_scope
Engine::run_ast_with_scope
Engine::run_file_with_scope [no_std]
Engine::compile_file_with_scope [no_std]
Engine::compile_expression_with_scope

[Variables] or [constants] defined at the global level of a script persist inside the custom `Scope`
even after the script ends.

```rust
let mut scope = Scope::new();

engine.run_with_scope(&mut scope, "let x = 42;")?;

// Variable 'x' stays inside the custom scope!
engine.run_with_scope(&mut scope, "print(x);")?;    //  prints 42
```

Due to [variable shadowing][shadowing], new [variables]/[constants] are simply added on top of
existing ones (even when they already exist), so care must be taken that new [variables]/[constants]
inside the custom `Scope` do not grow without bounds.

```rust
let mut scope = Scope::new();

// Don't do this - this creates 1 million variables named 'x'
//                 inside 'scope'!!!
for _ in 0..1_000_000 {
    engine.run_with_scope(&mut scope, "let x = 42;")?;
}

// The 'scope' contains a LOT of variables...
assert_eq!(scope.len(), 1_000_000);

// Variable 'x' stays inside the custom scope!
engine.run_with_scope(&mut scope, "print(x);")?;    //  prints 42
```

In order to remove [variables] or [constants] introduced by a script, use the `rewind` method.

```rust
// Run a million times
for _ in 0..1_000_000 {
    // Save the current size of the 'scope'
    let orig_scope_size = scope.len();

    engine.run_with_scope(&mut scope, "let x = 42;")?;

    // Rewind the 'scope' to the original size
    scope.rewind(orig_scope_size);
}

// The 'scope' is empty
assert_eq!(scope.len(), 0);

// Variable 'x' is no longer inside 'scope'!
engine.run_with_scope(&mut scope, "print(x);")?;    //  error: variable 'x' not found
```