Constants

Constants can be defined using the const keyword and are immutable.

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

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


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.

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


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 and indexers are always assumed to mutate the first &mut parameter and so they always raise errors when passed constants by default.

// 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

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.