146 lines
4.8 KiB
Markdown
146 lines
4.8 KiB
Markdown
Constants
|
|
=========
|
|
|
|
{{#include ../links.md}}
|
|
|
|
Constants can be defined using the `const` keyword and are immutable.
|
|
|
|
```rust
|
|
const X; // 'X' is a constant '()'
|
|
|
|
const X = 40 + 2; // 'X' is a constant 42
|
|
|
|
print(X * 2); // prints 84
|
|
|
|
X = 123; // <- syntax error: constant modified
|
|
```
|
|
|
|
```admonish tip.small "Tip: Naming"
|
|
|
|
Constants follow the same naming rules as [variables], but as a convention are often named with
|
|
all-capital letters.
|
|
```
|
|
|
|
|
|
Manually Add Constant into Custom Scope
|
|
---------------------------------------
|
|
|
|
```admonish tip.side "Tip: Singleton"
|
|
|
|
A constant value holding a [custom type] essentially acts
|
|
as a [_singleton_]({{rootUrl}}/patterns/singleton.md).
|
|
```
|
|
|
|
It is possible to add a constant into a custom [`Scope`] via `Scope::push_constant` so it'll be
|
|
available to scripts running with that [`Scope`].
|
|
|
|
```rust
|
|
use rhai::{Engine, Scope};
|
|
|
|
#[derive(Debug, Clone)]
|
|
struct TestStruct(i64); // custom type
|
|
|
|
let mut engine = Engine::new();
|
|
|
|
engine
|
|
.register_type_with_name::<TestStruct>("TestStruct") // register custom type
|
|
.register_get("value", |obj: &mut TestStruct| obj.0), // property getter
|
|
.register_fn("update_value",
|
|
|obj: &mut TestStruct, value: i64| obj.0 = value // mutating method
|
|
);
|
|
|
|
let script =
|
|
"
|
|
MY_NUMBER.update_value(42);
|
|
print(MY_NUMBER.value);
|
|
";
|
|
|
|
let ast = engine.compile(script)?;
|
|
|
|
let mut scope = Scope::new(); // create custom scope
|
|
|
|
scope.push_constant("MY_NUMBER", TestStruct(123_i64)); // add constant variable
|
|
|
|
// Beware: constant objects can still be modified via a method call!
|
|
engine.run_ast_with_scope(&mut scope, &ast)?; // prints 42
|
|
|
|
// Running the script directly, as below, is less desirable because
|
|
// the constant 'MY_NUMBER' will be propagated and copied into each usage
|
|
// during the script optimization step
|
|
engine.run_with_scope(&mut scope, script)?;
|
|
```
|
|
|
|
|
|
Caveat – Constants Can be Modified via Rust
|
|
-------------------------------------------------
|
|
|
|
```admonish tip.side.wide "Tip: Plugin functions"
|
|
|
|
In [plugin functions], `&mut` parameters disallow constant values by default.
|
|
|
|
This is different from the `Engine::register_XXX` API.
|
|
|
|
However, if a [plugin function] is marked with `#[export_fn(pure)]` or `#[rhai_fn(pure)]`,
|
|
it is assumed _pure_ (i.e. will not modify its arguments) and so constants are allowed.
|
|
```
|
|
|
|
A [custom type] stored as a constant cannot be modified via script, but _can_ be modified via a
|
|
registered Rust function that takes a first `&mut` parameter – because there is no way for
|
|
Rhai to know whether the Rust function modifies its argument!
|
|
|
|
By default, native Rust functions with a first `&mut` parameter always allow constants to be passed
|
|
to them. This is because using `&mut` can avoid unnecessary cloning of a [custom type] value, even
|
|
though it is actually not modified – for example returning the size of a collection type.
|
|
|
|
In line with intuition, Rhai is smart enough to always pass a _cloned copy_ of a constant as the
|
|
first `&mut` argument if the function is called in normal function call style.
|
|
|
|
If it is called as a [method], however, the Rust function will be able to modify the constant's value.
|
|
|
|
Also, property [setters][getters/setters] and [indexers] are always assumed to mutate the first
|
|
`&mut` parameter and so they always raise errors when passed constants by default.
|
|
|
|
```rust
|
|
// For the below, assume 'increment' is a Rust function with '&mut' first parameter
|
|
|
|
const X = 42; // a constant
|
|
|
|
increment(X); // call 'increment' in normal FUNCTION-CALL style
|
|
// since 'X' is constant, a COPY is passed instead
|
|
|
|
X == 42; // value is 'X" is unchanged
|
|
|
|
X.increment(); // call 'increment' in METHOD-CALL style
|
|
|
|
X == 43; // value of 'X' is changed!
|
|
// must use 'Dynamic::is_read_only' to check if parameter is constant
|
|
|
|
fn double() {
|
|
this *= 2; // function doubles 'this'
|
|
}
|
|
|
|
let y = 1; // 'y' is not constant and mutable
|
|
|
|
y.double(); // double it...
|
|
|
|
y == 2; // value of 'y' is changed as expected
|
|
|
|
X.double(); // since 'X' is constant, a COPY is passed to 'this'
|
|
|
|
X == 43; // value of 'X' is unchanged by script
|
|
```
|
|
|
|
```admonish info.small "Implications on script optimization"
|
|
|
|
Rhai _assumes_ that constants are never changed, even via Rust functions.
|
|
|
|
This is important to keep in mind because the script [optimizer][script optimization]
|
|
by default does _constant propagation_ as a operation.
|
|
|
|
If a constant is eventually modified by a Rust function, the optimizer will not see
|
|
the updated value and will propagate the original initialization value instead.
|
|
|
|
`Dynamic::is_read_only` can be used to detect whether a [`Dynamic`] value is constant or not within
|
|
a Rust function.
|
|
```
|