Variable Resolver

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)
    }
});

Benefits of using a variable resolver

  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.

Returned values are constants

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.

Tip: Returning shared values

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

This is one way to implement Mutable Global State.

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:

ParameterTypeDescription
name&strvariable name
indexusizean 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.
contextEvalContextmutable 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:

ValueDescription
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.