rhai/doc/src/engine/var.md
2020-11-20 16:52:28 +08:00

8.7 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 the Engine::on_var method:

let mut engine = Engine::new();

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

Returned Values are Constants

Variable values, if any returned, are treated as constants by the script and cannot be assigned to. This is to avoid needing a mutable reference to the underlying data provider which may not be possible to obtain.

In order to change these variables, it is best to push them into a custom [Scope] instead of using a variable resolver. Then these variables can be assigned to and their updated values read back after the script is evaluated.

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.

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>> + 'static

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 reference to the current evaluation context
- context.scope &Scope reference to the current [Scope] containing all variables up to the current evaluation position
- context.engine() &Engine reference to the current [Engine]
- context.imports() &Imports reference to the current stack of [modules] imported via import statements
- context.iter_namespaces() impl Iterator<Item = &Module> iterator of the namespaces (as [modules]) containing all script-defined functions
- context.this_ptr() Option<&Dynamic> reference to the current bound [this] pointer, if any
- context.call_level() usize the current nesting level of function calls

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(Dynamic)) value of the variable, treated as a constant
Err(Box<EvalAltResult>) error that is reflected back to the [Engine].
Normally this is EvalAltResult::ErrorVariableNotFound(var_name, Position::NONE) to indicate that the variable does not exist, but it can be any EvalAltResult.