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/_archive/rhai_engine/rhaibook/engine/var.md
2025-04-04 08:28:07 +02:00

5.9 KiB

Variable Resolver

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

By default, Rhai looks up access to [variables] from the enclosing block scope, working its way outwards until it reaches the top (global) level, then it searches the [Scope] that is passed into the Engine::eval call.

There is a built-in facility for advanced users to hook into the [variable] resolution service and to override its default behavior.

To do so, provide a closure to the [Engine] via Engine::on_var.

let mut engine = Engine::new();

// Register a variable resolver.
engine.on_var(|name, index, context| {
    match name {
        "MYSTIC_NUMBER" => Ok(Some(42_i64.into())),
        // Override a variable - make it not found even if it exists!
        "DO_NOT_USE" => Err(EvalAltResult::ErrorVariableNotFound(name.to_string(), Position::NONE).into()),
        // Silently maps 'chameleon' into 'innocent'.
        "chameleon" => context.scope().get_value("innocent").map(Some).ok_or_else(|| 
            EvalAltResult::ErrorVariableNotFound(name.to_string(), Position::NONE).into()
        ),
        // Return Ok(None) to continue with the normal variable resolution process.
        _ => Ok(None)
    }
});

1. Avoid having to maintain a custom [`Scope`] with all [variables] regardless of need
   (because a script may not use them all).

2. _Short-circuit_ [variable] access, essentially overriding standard behavior.

3. _Lazy-load_ [variables] when they are accessed, not up-front.
   This benefits when the number of [variables] is very large, when they are timing-dependent,
   or when they are expensive to load.

4. Rename system [variables] on a script-by-script basis without having to construct different [`Scope`]'s.

[Variable] values returned by a variable resolver are treated as _[constants]_.

This is to avoid needing a mutable reference to the underlying data provider which may not be possible to obtain.

To change these [variables], better push them into a custom [`Scope`] instead of
using a variable resolver.

It is possible to return a _shared_ value from a variable resolver.

This is one way to implement [Mutable Global State]({{rootUrl}}/patterns/global-mutable-state.md).

Function Signature

The function signature passed to Engine::on_var takes the following form.

Fn(name: &str, index: usize, context: EvalContext) -> Result<Option<Dynamic>, Box<EvalAltResult>>

where:

Parameter Type Description
name &str [variable] name
index usize an offset from the bottom of the current [Scope] that the [variable] is supposed to reside.
Offsets start from 1, with 1 meaning the last [variable] in the current [Scope]. Essentially the correct [variable] is at position scope.len() - index.
If index is zero, then there is no pre-calculated offset position and a search through the current [Scope] must be performed.
context [EvalContext] mutable reference to the current evaluation context

and [EvalContext] is a type that encapsulates the current evaluation context.

Return value

The return value is Result<Option<Dynamic>, Box<EvalAltResult>> where:

Value Description
Ok(None) normal [variable] resolution process should continue, i.e. continue searching through the [Scope]
Ok(Some(value)) value (a [Dynamic]) of the [variable], treated as a [constant]
Err(Box<EvalAltResult>) error that is reflected back to the [Engine], normally EvalAltResult::ErrorVariableNotFound to indicate that the [variable] does not exist, but it can be any EvalAltResult.