213 lines
5.8 KiB
Markdown
213 lines
5.8 KiB
Markdown
Constants Propagation
|
|
=====================
|
|
|
|
{{#include ../../links.md}}
|
|
|
|
```admonish tip.side
|
|
|
|
Effective in template-based machine-generated scripts to turn on/off certain sections.
|
|
```
|
|
|
|
[Constants] propagation is commonly used to:
|
|
|
|
* remove dead code,
|
|
|
|
* avoid [variable] lookups,
|
|
|
|
* [pre-calculate](op-eval.md) [constant] expressions.
|
|
|
|
```rust
|
|
const ABC = true;
|
|
const X = 41;
|
|
|
|
if ABC || calc(X+1) { print("done!"); } // 'ABC' is constant so replaced by 'true'...
|
|
// 'X' is constant so replaced by 41...
|
|
|
|
if true || calc(42) { print("done!"); } // '41+1' is replaced by 42
|
|
// since '||' short-circuits, 'calc' is never called
|
|
|
|
if true { print("done!"); } // <- the line above is equivalent to this
|
|
|
|
print("done!"); // <- the line above is further simplified to this
|
|
// because the condition is always true
|
|
```
|
|
|
|
~~~admonish tip "Tip: Custom `Scope` constants"
|
|
|
|
[Constant] values can be provided in a custom [`Scope`] object to the [`Engine`]
|
|
for optimization purposes.
|
|
|
|
```rust
|
|
use rhai::{Engine, Scope};
|
|
|
|
let engine = Engine::new();
|
|
|
|
let mut scope = Scope::new();
|
|
|
|
// Add constant to custom scope
|
|
scope.push_constant("ABC", true);
|
|
|
|
// Evaluate script with custom scope
|
|
engine.run_with_scope(&mut scope,
|
|
r#"
|
|
if ABC { // 'ABC' is replaced by 'true'
|
|
print("done!");
|
|
}
|
|
"#)?;
|
|
```
|
|
~~~
|
|
|
|
~~~admonish tip "Tip: Customer module constants"
|
|
|
|
[Constants] defined in [modules] that are registered into an [`Engine`] via
|
|
`Engine::register_global_module` are used in optimization.
|
|
|
|
```rust
|
|
use rhai::{Engine, Module};
|
|
|
|
let mut engine = Engine::new();
|
|
|
|
let mut module = Module::new();
|
|
|
|
// Add constant to module
|
|
module.set_var("ABC", true);
|
|
|
|
// Register global module
|
|
engine.register_global_module(module.into());
|
|
|
|
// Evaluate script
|
|
engine.run(
|
|
r#"
|
|
if ABC { // 'ABC' is replaced by 'true'
|
|
print("done!");
|
|
}
|
|
"#)?;
|
|
```
|
|
~~~
|
|
|
|
~~~admonish danger "Caveat: Constants in custom scope and modules are also propagated into functions"
|
|
|
|
[Constants] defined at _global_ level typically cannot be seen by script [functions] because they are _pure_.
|
|
|
|
```rust
|
|
const MY_CONSTANT = 42; // <- constant defined at global level
|
|
|
|
print(MY_CONSTANT); // <- optimized to: print(42)
|
|
|
|
fn foo() {
|
|
MY_CONSTANT // <- not optimized: 'foo' cannot see 'MY_CONSTANT'
|
|
}
|
|
|
|
print(foo()); // error: 'MY_CONSTANT' not found
|
|
```
|
|
|
|
When [constants] are provided in a custom [`Scope`] (e.g. via `Engine::compile_with_scope`,
|
|
`Engine::eval_with_scope` or `Engine::run_with_scope`), or in a [module] registered via
|
|
`Engine::register_global_module`, instead of defined within the same script, they are also
|
|
propagated to [functions].
|
|
|
|
This is usually the intuitive usage and behavior expected by regular users, even though it means
|
|
that a script will behave differently (essentially a runtime error) when [script optimization] is disabled.
|
|
|
|
```rust
|
|
use rhai::{Engine, Scope};
|
|
|
|
let engine = Engine::new();
|
|
|
|
let mut scope = Scope::new();
|
|
|
|
// Add constant to custom scope
|
|
scope.push_constant("MY_CONSTANT", 42_i64);
|
|
|
|
engine.run_with_scope(&mut scope,
|
|
"
|
|
print(MY_CONSTANT); // optimized to: print(42)
|
|
|
|
fn foo() {
|
|
MY_CONSTANT // optimized to: fn foo() { 42 }
|
|
}
|
|
|
|
print(foo()); // prints 42
|
|
")?;
|
|
```
|
|
|
|
The script will act differently when [script optimization] is disabled because script [functions]
|
|
are _pure_ and typically cannot see [constants] within the custom [`Scope`].
|
|
|
|
Therefore, constants in [functions] now throw a runtime error.
|
|
|
|
```rust
|
|
use rhai::{Engine, Scope, OptimizationLevel};
|
|
|
|
let mut engine = Engine::new();
|
|
|
|
// Turn off script optimization, no constants propagation is performed
|
|
engine.set_optimization_level(OptimizationLevel::None);
|
|
|
|
let mut scope = Scope::new();
|
|
|
|
// Add constant to custom scope
|
|
scope.push_constant("MY_CONSTANT", 42_i64);
|
|
|
|
engine.run_with_scope(&mut scope,
|
|
"
|
|
print(MY_CONSTANT); // prints 42
|
|
|
|
fn foo() {
|
|
MY_CONSTANT // <- 'foo' cannot see 'MY_CONSTANT'
|
|
}
|
|
|
|
print(foo()); // error: 'MY_CONSTANT' not found
|
|
")?;
|
|
```
|
|
~~~
|
|
|
|
~~~admonish danger "Caveat: Beware of large constants"
|
|
|
|
[Constants] propagation replaces each usage of the [constant] with a clone of its value.
|
|
|
|
This may have negative implications to performance if the [constant] value is expensive to clone
|
|
(e.g. if the type is very large).
|
|
|
|
```rust
|
|
let mut scope = Scope::new();
|
|
|
|
// Push a large constant into the scope...
|
|
let big_type = AVeryLargeType::take_long_time_to_create();
|
|
scope.push_constant("MY_BIG_TYPE", big_type);
|
|
|
|
// Causes each usage of 'MY_BIG_TYPE' in the script below to be replaced
|
|
// by cloned copies of 'AVeryLargeType'.
|
|
let result = engine.run_with_scope(&mut scope,
|
|
"
|
|
let value = MY_BIG_TYPE.value;
|
|
let data = MY_BIG_TYPE.data;
|
|
let len = MY_BIG_TYPE.len();
|
|
let has_options = MY_BIG_TYPE.has_options();
|
|
let num_options = MY_BIG_TYPE.options_len();
|
|
")?;
|
|
```
|
|
|
|
To avoid this, compile the script first to an [`AST`] _without_ the [constants], then evaluate the
|
|
[`AST`] (e.g. with `Engine::eval_ast_with_scope` or `Engine::run_ast_with_scope`) together with
|
|
the [constants].
|
|
~~~
|
|
|
|
~~~admonish danger "Caveat: Constants may be modified by Rust methods"
|
|
|
|
If the [constants] are modified later on (yes, it is possible, via Rust _methods_),
|
|
the modified values will not show up in the optimized script.
|
|
Only the initialization values of [constants] are ever retained.
|
|
|
|
```rust
|
|
const MY_SECRET_ANSWER = 42;
|
|
|
|
MY_SECRET_ANSWER.update_to(666); // assume 'update_to(&mut i64)' is a Rust function
|
|
|
|
print(MY_SECRET_ANSWER); // prints 42 because the constant is propagated
|
|
```
|
|
|
|
This is almost never a problem because real-world scripts seldom modify a [constant],
|
|
but the possibility is always there.
|
|
~~~
|