Expand section on modules and packages.

This commit is contained in:
Stephen Chung 2020-05-13 22:49:12 +08:00
parent cabceb7498
commit e6d6a709f0

View File

@ -21,9 +21,9 @@ Rhai's current features set:
* Fairly efficient (1 million iterations in 0.75 sec on my 5 year old laptop) * Fairly efficient (1 million iterations in 0.75 sec on my 5 year old laptop)
* Low compile-time overhead (~0.6 sec debug/~3 sec release for script runner app) * Low compile-time overhead (~0.6 sec debug/~3 sec release for script runner app)
* [`no-std`](#optional-features) support * [`no-std`](#optional-features) support
* Support for [function overloading](#function-overloading) * [Function overloading](#function-overloading)
* Support for [operator overloading](#operator-overloading) * [Operator overloading](#operator-overloading)
* Support for loading external [modules] * [Modules]
* Compiled script is [optimized](#script-optimization) for repeat evaluations * Compiled script is [optimized](#script-optimization) for repeat evaluations
* Support for [minimal builds](#minimal-builds) by excluding unneeded language [features](#optional-features) * Support for [minimal builds](#minimal-builds) by excluding unneeded language [features](#optional-features)
* Very few additional dependencies (right now only [`num-traits`](https://crates.io/crates/num-traits/) * Very few additional dependencies (right now only [`num-traits`](https://crates.io/crates/num-traits/)
@ -126,7 +126,8 @@ Disable script-defined functions (`no_function`) only when the feature is not ne
[`Engine::new_raw`](#raw-engine) creates a _raw_ engine which does not register _any_ utility functions. [`Engine::new_raw`](#raw-engine) creates a _raw_ engine which does not register _any_ utility functions.
This makes the scripting language quite useless as even basic arithmetic operators are not supported. This makes the scripting language quite useless as even basic arithmetic operators are not supported.
Selectively include the necessary operators by loading specific [packages](#packages) while minimizing the code footprint. Selectively include the necessary functionalities by loading specific [packages](#packages) to minimize the footprint.
Packages are sharable (even across threads via the [`sync`] feature), so they only have to be created once.
Related Related
------- -------
@ -371,7 +372,7 @@ Use `Engine::new_raw` to create a _raw_ `Engine`, in which _nothing_ is added, n
### Packages ### Packages
Rhai functional features are provided in different _packages_ that can be loaded via a call to `load_package`. Rhai functional features are provided in different _packages_ that can be loaded via a call to `load_package`.
Packages reside under `rhai::packages::*` and the trait `rhai::packages::Package` must be imported in order for Packages reside under `rhai::packages::*` and the trait `rhai::packages::Package` must be loaded in order for
packages to be used. packages to be used.
```rust ```rust
@ -382,7 +383,7 @@ use rhai::packages::CorePackage; // the 'core' package contains b
let mut engine = Engine::new_raw(); // create a 'raw' Engine let mut engine = Engine::new_raw(); // create a 'raw' Engine
let package = CorePackage::new(); // create a package - can be shared among multiple `Engine` instances let package = CorePackage::new(); // create a package - can be shared among multiple `Engine` instances
engine.load_package(package.get()); // load the package manually engine.load_package(package.get()); // load the package manually. 'get' returns a reference to the shared package
``` ```
The follow packages are available: The follow packages are available:
@ -401,6 +402,20 @@ The follow packages are available:
| `CorePackage` | Basic essentials | | | | `CorePackage` | Basic essentials | | |
| `StandardPackage` | Standard library | | | | `StandardPackage` | Standard library | | |
Packages typically contain Rust functions that are callable within a Rhai script.
All functions registered in a package is loaded under the _global namespace_ (i.e. they're available without module qualifiers).
Once a package is created (e.g. via `new`), it can be _shared_ (via `get`) among multiple instances of [`Engine`],
even across threads (if the [`sync`] feature is turned on).
Therefore, a package only has to be created _once_.
Packages are actually implemented as [modules], so they share a lot of behavior and characteristics.
The main difference is that a package loads under the _global_ namespace, while a module loads under its own
namespace alias specified in an `import` statement (see also [modules]).
A package is _static_ (i.e. pre-loaded into an [`Engine`]), while a module is _dynamic_ (i.e. loaded with
the `import` statement).
Custom packages can also be created. See the macro [`def_package!`](https://docs.rs/rhai/0.13.0/rhai/macro.def_package.html).
Evaluate expressions only Evaluate expressions only
------------------------- -------------------------
@ -762,11 +777,17 @@ println!("result: {}", result); // prints 42
let result: f64 = engine.eval("1.0 + 0.0"); // '+' operator for two floats not overloaded let result: f64 = engine.eval("1.0 + 0.0"); // '+' operator for two floats not overloaded
println!("result: {}", result); // prints 1.0 println!("result: {}", result); // prints 1.0
fn mixed_add(a: i64, b: f64) -> f64 { (a as f64) + b }
engine.register_fn("+", mixed_add); // register '+' operator for an integer and a float
let result: i64 = engine.eval("1 + 1.0"); // prints 2.0 (normally an error)
``` ```
Use operator overloading for custom types (described below) only. Be very careful when overloading built-in operators because Use operator overloading for custom types (described below) only.
script writers expect standard operators to behave in a consistent and predictable manner, and will be annoyed if a calculation Be very careful when overloading built-in operators because script writers expect standard operators to behave in a
for '+' turns into a subtraction, for example. consistent and predictable manner, and will be annoyed if a calculation for '`+`' turns into a subtraction, for example.
Operator overloading also impacts script optimization when using [`OptimizationLevel::Full`]. Operator overloading also impacts script optimization when using [`OptimizationLevel::Full`].
See the [relevant section](#script-optimization) for more details. See the [relevant section](#script-optimization) for more details.
@ -2048,21 +2069,21 @@ for entry in logbook.read().unwrap().iter() {
} }
``` ```
Using external modules Modules
---------------------- -------
[module]: #using-external-modules [module]: #modules
[modules]: #using-external-modules [modules]: #modules
Rhai allows organizing code (functions and variables) into _modules_. Rhai allows organizing code (functions, both Rust-based or script-based, and variables) into _modules_.
Modules can be disabled via the [`no_module`] feature. Modules can be disabled via the [`no_module`] feature.
### Exporting variables and functions ### Exporting variables and functions from modules
A module is a single script (or pre-compiled `AST`) containing global variables and functions. A _module_ is a single script (or pre-compiled `AST`) containing global variables and functions.
The `export` statement, which can only be at global level, exposes selected variables as members of a module. The `export` statement, which can only be at global level, exposes selected variables as members of a module.
Variables not exported are private and invisible to the outside. Variables not exported are _private_ and invisible to the outside.
All functions are automatically exported, unless it is prefixed with `private`. On the other hand, all functions are automatically exported, _unless_ it is explicitly opt-out with the `private` prefix.
Functions declared `private` are invisible to the outside. Functions declared `private` are invisible to the outside.
Everything exported from a module is **constant** (**read-only**). Everything exported from a module is **constant** (**read-only**).
@ -2070,11 +2091,11 @@ Everything exported from a module is **constant** (**read-only**).
```rust ```rust
// This is a module script. // This is a module script.
fn inc(x) { x + 1 } // public function fn inc(x) { x + 1 } // script-defined function - default public
private fn foo() {} // private function - invisible to outside private fn foo() {} // private function - invisible to outside
let private = 123; // variable not exported - invisible to outside let private = 123; // variable not exported - default invisible to outside
let x = 42; // this will be exported below let x = 42; // this will be exported below
export x; // the variable 'x' is exported under its own name export x; // the variable 'x' is exported under its own name
@ -2085,19 +2106,25 @@ export x as answer; // the variable 'x' is exported under the alias 'ans
### Importing modules ### Importing modules
A module can be _imported_ via the `import` statement, and its members accessed via '`::`' similar to C++. A module can be _imported_ via the `import` statement, and its members are accessed via '`::`' similar to C++.
```rust ```rust
import "crypto" as crypto; // import the script file 'crypto.rhai' as a module import "crypto" as crypto; // import the script file 'crypto.rhai' as a module
crypto::encrypt(secret); // use functions defined under the module via '::' crypto::encrypt(secret); // use functions defined under the module via '::'
crypto::hash::sha256(key); // sub-modules are also supported
print(crypto::status); // module variables are constants print(crypto::status); // module variables are constants
crypto::hash::sha256(key); // sub-modules are also supported crypto::status = "off"; // <- runtime error - cannot modify a constant
``` ```
`import` statements are _scoped_, meaning that they are only accessible inside the scope that they're imported. `import` statements are _scoped_, meaning that they are only accessible inside the scope that they're imported.
They can appear anywhere a normal statement can be, but in the vast majority of cases `import` statements are
group at the beginning of a script. It is not advised to deviate from this common practice unless there is
a _Very Good Reason™_. Especially, do not place an `import` statement within a loop; doing so will repeatedly
re-load the same module during every iteration of the loop!
```rust ```rust
let mod = "crypto"; let mod = "crypto";
@ -2110,9 +2137,15 @@ if secured { // new block scope
crypto::encrypt(others); // <- this causes a run-time error because the 'crypto' module crypto::encrypt(others); // <- this causes a run-time error because the 'crypto' module
// is no longer available! // is no longer available!
for x in range(0, 1000) {
import "crypto" as c; // <- importing a module inside a loop is a Very Bad Idea
c.encrypt(something);
}
``` ```
### Creating custom modules from Rust ### Creating custom modules with Rust
To load a custom module (written in Rust) into an [`Engine`], first create a `Module` type, add variables/functions into it, To load a custom module (written in Rust) into an [`Engine`], first create a `Module` type, add variables/functions into it,
then finally push it into a custom [`Scope`]. This has the equivalent effect of putting an `import` statement then finally push it into a custom [`Scope`]. This has the equivalent effect of putting an `import` statement
@ -2141,8 +2174,9 @@ engine.eval_expression_with_scope::<i64>(&scope, "question::inc(question::answer
### Creating a module from an `AST` ### Creating a module from an `AST`
It is easy to convert a pre-compiled `AST` into a module, just use `Module::eval_ast_as_new`. It is easy to convert a pre-compiled `AST` into a module: just use `Module::eval_ast_as_new`.
Don't forget the `export` statement, otherwise there will be nothing inside the module! Don't forget the `export` statement, otherwise there will be no variables exposed by the module
other than non-`private` functions (unless that's intentional).
```rust ```rust
use rhai::{Engine, Module}; use rhai::{Engine, Module};