This commit is contained in:
2025-04-19 08:10:30 +02:00
parent 6389da0372
commit 1157d6da64
327 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,146 @@
Register a Custom Type via the Type Builder
===========================================
{{#include ../links.md}}
```admonish warning.small "Warning"
This assumes that the type is defined within the current crate and you can implement traits for it.
However, you may not _control_ the type (it may be auto-generated or maintained by another user),
so you cannot put attributes on it.
```
It is usually convenient to package a [custom type]'s API (i.e. [methods],
[properties][getters/setters], [indexers] and [type iterators]) together such that they can be more
easily managed.
This can be achieved by manually implementing the `CustomType` trait, which contains only a single method:
> ```rust
> fn build(builder: TypeBuilder<T>)
> ```
The `TypeBuilder` parameter provides a range of convenient methods to register [methods], property
[getters/setters], [indexers] and [type iterators] of a [custom type]:
| Method | Description |
| ---------------------- | ------------------------------------------------------------------------- |
| `with_name` | set a friendly name |
| `on_print` | register the [`to_string`] function that pretty-prints the [custom type] |
| `on_debug` | register the [`to_debug`] function that debug-prints the [custom type] |
| `with_fn` | register a [method] (or any function really) |
| `with_get` | register a property [getter][getters/setters] |
| `with_set` | register a property [getter][getters/setters] |
| `with_get_set` | register property [getters/setters] |
| `with_indexer_get` | register an [indexer] get function |
| `with_indexer_set` | register an [indexer] set function |
| `with_indexer_get_set` | register [indexer] get/set functions |
| `is_iterable` | automatically register a [type iterator] if the [custom type] is iterable |
```admonish tip.small "Tip: Use plugin module if starting from scratch"
The `CustomType` trait is typically used on external types that are already defined.
To define a [custom type] and implement its API from scratch, it is more convenient to use a [plugin module].
```
Example
-------
```rust
// Custom type
#[derive(Debug, Clone, Eq, PartialEq)]
struct Vec3 {
x: i64,
y: i64,
z: i64,
}
// Custom type API
impl Vec3 {
fn new(x: i64, y: i64, z: i64) -> Self {
Self { x, y, z }
}
fn get_x(&mut self) -> i64 {
self.x
}
fn set_x(&mut self, x: i64) {
self.x = x
}
fn get_y(&mut self) -> i64 {
self.y
}
fn set_y(&mut self, y: i64) {
self.y = y
}
fn get_z(&mut self) -> i64 {
self.z
}
fn set_z(&mut self, z: i64) {
self.z = z
}
}
// The custom type can even be iterated!
impl IntoIterator for Vec3 {
type Item = i64;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
vec![self.x, self.y, self.z].into_iter()
}
}
// Use 'CustomType' to register the entire API
impl CustomType for Vec3 {
fn build(mut builder: TypeBuilder<Self>) {
builder
.with_name("Vec3")
.with_fn("vec3", Self::new)
.is_iterable()
.with_get_set("x", Self::get_x, Self::set_x)
.with_get_set("y", Self::get_y, Self::set_y)
.with_get_set("z", Self::get_z, Self::set_z)
// Indexer get/set functions that do not panic on invalid indices
.with_indexer_get_set(
|vec: &mut Self, idx: i64) -> Result<i64, Box<EvalAltResult>> {
match idx {
0 => Ok(vec.x),
1 => Ok(vec.y),
2 => Ok(vec.z),
_ => Err(EvalAltResult::ErrorIndexNotFound(idx.Into(), Position::NONE).into()),
}
},
|vec: &mut Self, idx: i64, value: i64) -> Result<(), Box<EvalAltResult>> {
match idx {
0 => vec.x = value,
1 => vec.y = value,
2 => vec.z = value,
_ => Err(EvalAltResult::ErrorIndexNotFound(idx.Into(), Position::NONE).into()),
}
Ok(())
}
);
}
}
let mut engine = Engine::new();
// Register the custom type in one go!
engine.build_type::<Vec3>();
```
~~~admonish question "TL;DR &ndash; Why isn't there `is_indexable`?"
Technically speaking, `TypeBuilder` can automatically register an [indexer] get function if the [custom type] implements `Index`.
Similarly, it can automatically register an [indexer] set function for `IndexMut`.
In practice, however, this is usually not desirable because most `Index`/`IndexMut` implementations panic on invalid indices.
For Rhai, it is necessary to handle invalid indices properly by returning an error.
Therefore, in the example above, the `with_indexer_get_set` method properly handles invalid indices by returning errors.
~~~

View File

@@ -0,0 +1,139 @@
Custom Collection Types
=======================
{{#include ../links.md}}
~~~admonish tip.side "Tip"
Collections can also hold [`Dynamic`] values (e.g. like an [array]).
~~~
A _collection_ type holds a... well... _collection_ of items. It can be homogeneous (all items are
the same type) or heterogeneous (items are of different types, use [`Dynamic`] to hold).
Because their only purpose for existence is to hold a number of items, collection types commonly
register the following methods.
<section></section>
| Method | Description |
| ------------------------- | ---------------------------------------------------------------- |
| `len` method and property | gets the total number of items in the collection |
| `clear` | clears the collection |
| `contains` | checks if a particular item exists in the collection |
| `add`, `+=` operator | adds a particular item to the collection |
| `remove`, `-=` operator | removes a particular item from the collection |
| `merge` or `+` operator | merges two collections, yielding a new collection with all items |
```admonish tip.small "Tip: Define type iterator"
Collections are typically iterable.
It is customary to use `Engine::register_iterator` to allow iterating the collection if
it implements `IntoIterator`.
Alternative, register a specific [type iterator] for the [custom type].
```
```admonish tip.small "Tip: Use a plugin module"
A [plugin module] makes defining an entire API for a [custom type] a snap.
```
Example
-------
```rust
type MyBag = HashSet<MyItem>;
engine
.register_type_with_name::<MyBag>("MyBag")
.register_iterator::<MyBag>()
.register_fn("new_bag", || MyBag::new())
.register_fn("len", |col: &mut MyBag| col.len() as i64)
.register_get("len", |col: &mut MyBag| col.len() as i64)
.register_fn("clear", |col: &mut MyBag| col.clear())
.register_fn("contains", |col: &mut MyBag, item: i64| col.contains(&item))
.register_fn("add", |col: &mut MyBag, item: MyItem| col.insert(item))
.register_fn("+=", |col: &mut MyBag, item: MyItem| col.insert(item))
.register_fn("remove", |col: &mut MyBag, item: MyItem| col.remove(&item))
.register_fn("-=", |col: &mut MyBag, item: MyItem| col.remove(&item))
.register_fn("+", |mut col1: MyBag, col2: MyBag| {
col1.extend(col2.into_iter());
col1
});
```
What About Indexers?
--------------------
Many users are tempted to register [indexers] for custom collections. This essentially makes the
original Rust type something similar to `Vec<MyType>`.
Rhai's standard [`Array`] type is `Vec<Dynamic>` which already holds an ordered, iterable and
indexable collection of dynamic items. Since Rhai has built-in support, manipulating [arrays] is fast.
In _most_ circumstances, it is better to use [`Array`] instead of a [custom type].
~~~admonish tip.small "Tip: Convert to `Array` using `.into()`"
[`Dynamic`] implements `FromIterator` for all iterable types and an [`Array`] is created in the process.
So, converting a typed array (i.e. `Vec<MyType>`) into an [array] in Rhai is as simple as calling `.into()`.
```rust
// Say you have a custom typed array...
let my_custom_array: Vec<MyType> = do_lots_of_calc(42);
// Convert it into a 'Dynamic' that holds an array
let value: Dynamic = my_custom_array.into();
// Use is anywhere in Rhai...
scope.push("my_custom_array", value);
engine
// Raw function that returns a custom type
.register_fn("do_lots_of_calc_raw", do_lots_of_calc)
// Wrap function that return a custom typed array
.register_fn("do_lots_of_calc", |seed: i64| -> Dynamic {
let result = do_lots_of_calc(seed); // Vec<MyType>
result.into() // Array in Dynamic
});
```
~~~
TL;DR
-----
~~~admonish question "Why shouldn't we register `Vec<MyType>`?"
### Reason #1: Performance
A main reason why anybody would want to do this is to avoid the overhead of storing [`Dynamic`] items.
This is why [BLOB's] is a built-in data type in Rhai, even though it is actually defined as `Vec<u8>`.
The overhead of using [`Dynamic`] (16 bytes) versus `u8` (1 byte) is worth the trouble, although the
performance gains may not be as pronounced as expected: benchmarks show a 15% speed improvement inside
a tight loop compared with using an [array].
`Vec<MyType>`, however, will be treated as an opaque [custom type] in Rhai, so performance is not optimized.
What you gain from avoiding [`Dynamic`], you pay back in terms of slower access to the `Vec` as well as `MyType`
(which is treated as yet another opaque [custom type]).
### Reason #2: API
Another reason why it shouldn't be done is due to the large number of functions and methods that must be registered
for each type of this sort. One only has to look at the vast API surface of [arrays]({{rootUrl}}/language/arrays.md#built-in-functions)
to see the common methods that a user would expect to be available.
Since `Vec<Type>` looks, feels and quacks just like a normal [array], and the usage syntax is almost equivalent (except
for the fact that the data type is restricted), users would be frustrated if they find that certain functions available for
[arrays] are not provided.
This is similar to JavaScript's [_Typed Arrays_](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays).
They are quite awkward to work with, and basically each has a full API definition that must be pre-registered.
~~~

View File

@@ -0,0 +1,38 @@
Advanced Usage &ndash; Restore `NativeCallContext`
==================================================
{{#include ../links.md}}
The [`NativeCallContext`] type encapsulates the entire _context_ of a script up to the
particular point of the native Rust function call.
The data inside a [`NativeCallContext`] can be stored (as a type `NativeCallContextStore`) for later
use, when a new [`NativeCallContext`] can be constructed based on these stored data.
A reconstructed [`NativeCallContext`] acts almost the same as the original instance, so it is possible
to suspend the evaluation of a script, and to continue at a later time with a new
[`NativeCallContext`].
Doing so requires the [`internals`] feature to access internal APIs.
### Step 1: Store `NativeCallContext` data
```rust
// Store context for later use
let context_data = context.store_data();
// ... store 'context_data' somewhere ...
secret_database.push(context_data);
```
### Step 2: Restore `NativeCallContext`
```rust
// ... do something else ...
// Restore the context
let context_data = secret_database.get();
let new_context = context_data.create_context(&engine);
```

View File

@@ -0,0 +1,110 @@
`NativeCallContext`
===================
{{#include ../links.md}}
If the _first_ parameter of a function is of type `rhai::NativeCallContext`, then it is treated
specially by the [`Engine`].
`NativeCallContext` is a type that encapsulates the current _call context_ of a Rust function call
and exposes the following.
| Method | Return type | Description |
| ------------------------- | :----------------------------------------------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `engine()` | [`&Engine`][`Engine`] | the current [`Engine`], with all configurations and settings.<br/>This is sometimes useful for calling a script-defined function within the same evaluation context using [`Engine::call_fn`][`call_fn`], or calling a [function pointer]. |
| `fn_name()` | `&str` | name of the function called (useful when the same Rust function is mapped to multiple Rhai-callable function names) |
| `source()` | `Option<&str>` | reference to the current source, if any |
| `position()` | `Position` | position of the function call |
| `call_level()` | `usize` | the current nesting level of function calls |
| `tag()` | [`&Dynamic`][`Dynamic`] | reference to the _custom state_ that is persistent during the current run |
| `iter_imports()` | `impl Iterator<Item = (&str,`[`&Module`][`Module`]`)>` | iterator of the current stack of [modules] imported via `import` statements, in reverse order (i.e. later [modules] come first) |
| `global_runtime_state()` | [`&GlobalRuntimeState`][`GlobalRuntimeState`] | reference to the current [global runtime state][`GlobalRuntimeState`] (including the stack of [modules] imported via `import` statements); requires the [`internals`] feature |
| `iter_namespaces()` | `impl Iterator<Item =`[`&Module`][`Module`]`>` | iterator of the [namespaces][function namespaces] (as [modules]) containing all script-defined [functions], in reverse order (i.e. later [modules] come first) |
| `namespaces()` | [`&[&Module]`][`Module`] | reference to the [namespaces][function namespaces] (as [modules]) containing all script-defined [functions]; requires the [`internals`] feature |
| `call_fn(...)` | `Result<T, Box<EvalAltResult>>` | call a function with the supplied arguments, casting the result into the required type |
| `call_native_fn(...)` | `Result<T, Box<EvalAltResult>>` | call a registered native Rust function with the supplied arguments, casting the result into the required type |
| `call_fn_raw(...)` | `Result<`[`Dynamic`]`, Box<EvalAltResult>>` | call a function with the supplied arguments; this is an advanced method |
| `call_native_fn_raw(...)` | `Result<`[`Dynamic`]`, Box<EvalAltResult>>` | call a registered native Rust function with the supplied arguments; this is an advanced method |
Example Implementations
-----------------------
~~~admonish example "Example &ndash; Implement Safety Checks"
The native call context is useful for protecting a function from malicious scripts.
```rust
use rhai::{Array, NativeCallContext, EvalAltResult, Position};
// This function builds an array of arbitrary size, but is protected against attacks
// by first checking with the allowed limit set into the 'Engine'.
pub fn new_array(context: NativeCallContext, size: i64) -> Result<Array, Box<EvalAltResult>>
{
let array = Array::new();
if size <= 0 {
return array;
}
let size = size as usize;
let max_size = context.engine().max_array_size();
// Make sure the function does not generate a data structure larger than
// the allowed limit for the Engine!
if max_size > 0 && size > max_size {
return Err(EvalAltResult::ErrorDataTooLarge(
"Size to grow".to_string(),
max_size, size,
context.position(),
).into());
}
for x in 0..size {
array.push(x.into());
}
OK(array)
}
```
~~~
~~~admonish example "Example &ndash; Call a Function Within a Function"
The _native call context_ can be used to call a [function] within the current evaluation
via `call_fn` (or more commonly `call_native_fn`).
```rust
use rhai::{Engine, NativeCallContext};
let mut engine = Engine::new();
// A function expecting a callback in form of a function pointer.
engine.register_fn("super_call", |context: NativeCallContext, value: i64| {
// Call a function within the current evaluation!
// 'call_native_fn' ensures that only registered native Rust functions
// are called, so a scripted function named 'double' cannot hijack
// the process.
// To also include scripted functions, use 'call_fn' instead.
context.call_native_fn::<i64>("double", (value,))
// ^^^^^^^^ arguments passed in tuple
});
```
~~~
~~~admonish example "Example &ndash; Implement a Callback"
The _native call context_ can be used to call a [function pointer] or [closure] that has been passed
as a parameter to the function (via `FnPtr::call_within_context`), thereby implementing a _callback_.
```rust
use rhai::{Dynamic, FnPtr, NativeCallContext, EvalAltResult};
pub fn greet(context: NativeCallContext, callback: FnPtr) -> Result<String, Box<EvalAltResult>>
{
// Call the callback closure with the current evaluation context!
let name = callback.call_within_context(&context, ())?;
Ok(format!("hello, {}!", name))
}
```
~~~

View File

@@ -0,0 +1,97 @@
Working with Any Rust Type
===========================
{{#include ../links.md}}
```admonish tip.side "Tip: Shared types"
The only requirement of a type to work with Rhai is `Clone`.
Therefore, it is extremely easy to use Rhai with data types such as
`Rc<...>`, `Arc<...>`, `Rc<RefCell<...>>`, `Arc<Mutex<...>>` etc.
```
~~~admonish note.side "Under `sync`"
If the [`sync`] feature is used, a custom type must also be `Send + Sync`.
~~~
Rhai works seamlessly with any Rust type, as long as it implements `Clone` as this allows the
[`Engine`] to pass by value.
A type that is not one of the [standard types] is termed a "custom type".
Custom types can have the following:
* a custom (friendly) display name
* [methods]
* [property getters and setters](getters/setters)
* [indexers]
Free Typing
-----------
```admonish question.side "Why \\"Custom\\"?"
Rhai internally supports a number of standard data types (see [this list][standard types]).
Any type outside of the list is considered _custom_.
```
```admonish warning.side "Custom types are slower"
Custom types run _slower_ than [built-in types][standard types] due to an additional
level of indirection, but for all other purposes there is no difference.
```
Rhai works seamlessly with _any_ Rust type.
A custom type is stored in Rhai as a Rust _trait object_ (specifically, a `dyn rhai::Variant`),
with no restrictions other than being `Clone` (plus `Send + Sync` under the [`sync`] feature).
The type literally does not have any prerequisite other than being `Clone`.
It does not need to implement any other trait or use any custom `#[derive]`.
This allows Rhai to be integrated into an existing Rust code base with as little plumbing as
possible, usually silently and seamlessly.
External types that are not defined within the same crate (and thus cannot implement special Rhai
traits or use special `#[derive]`) can also be used easily with Rhai.
Support for custom types can be turned off via the [`no_object`] feature.
Register API
------------
For Rhai scripts to interact with the custom type, and API must be registered for it with the [`Engine`].
The API can consist of functions, [methods], property [getters/setters], [indexers],
[iterators][type iterators] etc.
There are three ways to register an API for a custom type.
### 1. Auto-Generate API
If you have complete control of the type, then this is the easiest way.
The [`#[derive(CustomType)]`](derive-custom-type.md) macro can be used to automatically generate an
API for a custom type via the [`CustomType`] trait.
### 2. Custom Type Builder
For types in the same crate that you do not control, each function, [method], property [getter/setter][getters/setters],
[indexer] and [iterator][type iterator] can be registered manually, as a single package, via the [`CustomType`] trait
using the _Custom Type Builder_.
### 3. Manual Registration
For external types that cannot implement the [`CustomType`] trait due to Rust's [_orphan rule_](https://doc.rust-lang.org/book/ch10-02-traits.html),
each function, [method], property [getter/setter][getters/setters], [indexer] and [iterator][type iterator]
must be registered manually with the [`Engine`].

View File

@@ -0,0 +1,227 @@
Auto-Generate API for Custom Type
=================================
{{#include ../links.md}}
```admonish warning.small "Warning"
This assumes that you have complete control of the type and can do whatever you want with it
(such as putting attributes on fields).
In particular, the type must be defined within the current crate.
```
To register a type and its API for use with an [`Engine`], the simplest method is via the [`CustomType`] trait.
A custom derive macro is provided to auto-implement [`CustomType`] on any `struct` type,
which exposes all the type's fields to an [`Engine`] all at once.
It is as simple as adding `#[derive(CustomType)]` to the type definition.
```rust
use rhai::{CustomType, TypeBuilder}; // <- necessary imports
#[derive(Clone, CustomType)] // <- auto-implement 'CustomType'
pub struct Vec3 { // for normal structs
pub x: i64,
pub y: i64,
pub z: i64,
}
#[derive(Clone, CustomType)] // <- auto-implement 'CustomType'
pub struct ABC(i64, bool, String); // for tuple structs
let mut engine = Engine::new();
// Register the custom types!
engine.build_type::<Vec3>()
.build_type::<ABC>();
```
Custom Attribute Options
------------------------
The `rhai_type` attribute, with options, can be added to the fields of the type to customize the auto-generated API.
| Option | Applies to | Value | Description |
| :--------: | :---------: | :---------------: | ------------------------------------------------------------------------------------------------------------------------ |
| `name` | type, field | string expression | use this name instead of the type/field name. |
| `skip` | field | _none_ | skip this field; cannot be used with any other attribute. |
| `readonly` | field | _none_ | only auto-generate getter, no setter; cannot be used with `set`. |
| `get` | field | function path | use this getter function (with `&self`) instead of the auto-generated getter; if `get_mut` is also set, this is ignored. |
| `get_mut` | field | function path | use this getter function (with `&mut self`) instead of the auto-generated getter. |
| `set` | field | function path | use this setter function instead of the auto-generated setter; cannot be used with `readonly`. |
| `extra` | type | function path | call this function after building the type to add additional APIs |
### Function signatures
The signature of the function for `get` is:
> ```rust
> Fn(&T) -> V
> ```
The signature of the function for `get_mut` is:
> ```rust
> Fn(&mut T) -> V
> ```
The signature of the function for `set` is:
> ```rust
> Fn(&mut T, V)
> ```
The signature of the function for `extra` is:
> ```rust
> Fn(&mut TypeBuilder<T>)
> ```
### Example
```rust
use rhai::{CustomType, TypeBuilder}; // <- necessary imports
#[derive(Debug, Clone)]
#[derive(CustomType)] // <- auto-implement 'CustomType'
pub struct ABC(
#[rhai_type(skip)] // <- 'field0' not included
i64,
#[rhai_type(readonly)] // <- only auto getter, no setter for 'field1'
i64,
#[rhai_type(name = "flag")] // <- override property name for 'field2'
bool,
String // <- auto getter/setter for 'field3'
);
#[derive(Default, Clone)]
#[derive(CustomType)] // <- auto-implement 'CustomType'
#[rhai_type(name = "MyFoo", extra = Self::build_extra)] // <- call this type 'MyFoo' and use 'build_extra' to add additional APIs
pub struct Foo {
#[rhai_type(skip)] // <- field not included
dummy: i64,
#[rhai_type(readonly)] // <- only auto getter, no setter for 'bar'
bar: i64,
#[rhai_type(name = "flag")] // <- override property name
baz: bool, // <- auto getter/setter for 'baz'
#[rhai_type(get = Self::qux)] // <- call custom getter (with '&self') for 'qux'
qux: char, // <- auto setter for 'qux'
#[rhai_type(set = Self::set_hello)] // <- call custom setter for 'hello'
hello: String // <- auto getter for 'hello'
}
impl Foo {
/// Regular field getter function with `&self`
pub fn qux(&self) -> char {
self.qux
}
/// Special setter implementation for `hello`
pub fn set_hello(&mut self, value: String) {
self.hello = if self.baz {
let mut s = self.hello.clone();
s.push_str(&value);
for _ in 0..self.bar { s.push('!'); }
s
} else {
value
};
}
/// Additional APIs
fn build_extra(builder: &mut TypeBuilder<Self>) {
// Register constructor function
builder.with_fn("new_foo", || Self::default());
}
}
#[derive(Debug, Clone, Eq, PartialEq, CustomType)]
#[rhai_fn(extra = vec3_build_extra)]
pub struct Vec3 {
#[rhai_type(get = Self::x, set = Self::set_x)]
x: i64,
#[rhai_type(get = Self::y, set = Self::set_y)]
y: i64,
#[rhai_type(get = Self::z, set = Self::set_z)]
z: i64,
}
impl Vec3 {
fn new(x: i64, y: i64, z: i64) -> Self { Self { x, y, z } }
fn x(&self) -> i64 { self.x }
fn set_x(&mut self, x: i64) { self.x = x }
fn y(&self) -> i64 { self.y }
fn set_y(&mut self, y: i64) { self.y = y }
fn z(&self) -> i64 { self.z }
fn set_z(&mut self, z: i64) { self.z = z }
}
fn vec3_build_extra(builder: &mut TypeBuilder<Self>) {
// Register constructor function
builder.with_fn("Vec3", Self::new);
}
```
~~~admonish question "TL;DR &ndash; The above is equivalent to this..."
```rust
impl CustomType for ABC {
fn build(mut builder: TypeBuilder<Self>)
{
builder.with_name("ABC");
builder.with_get("field1", |obj: &mut Self| obj.1.clone());
builder.with_get_set("flag",
|obj: &mut Self| obj.2.clone(),
|obj: &mut Self, val| obj.2 = val
);
builder.with_get_set("field3",
|obj: &mut Self| obj.3.clone(),
|obj: &mut Self, val| obj.3 = val
);
}
}
impl CustomType for Foo {
fn build(mut builder: TypeBuilder<Self>)
{
builder.with_name("MyFoo");
builder.with_get("bar", |obj: &mut Self| obj.bar.clone());
builder.with_get_set("flag",
|obj: &mut Self| obj.baz.clone(),
|obj: &mut Self, val| obj.baz = val
);
builder.with_get_set("qux",
|obj: &Self| Self::qux(&*obj)),
|obj: &mut Self, val| obj.qux = val
)
builder.with_get_set("hello",
|obj: &mut Self| obj.hello.clone(),
Self::set_hello
);
Self::build_extra(&mut builder);
}
}
impl CustomType for Vec3 {
fn build(mut builder: TypeBuilder<Self>)
{
builder.with_name("Vec3");
builder.with_get_set("x", |obj: &mut Self| Self::x(&*obj), Self::set_x);
builder.with_get_set("y", |obj: &mut Self| Self::y(&*obj), Self::set_y);
builder.with_get_set("z", |obj: &mut Self| Self::z(&*obj), Self::set_z);
vec3_build_extra(&mut builder);
}
}
```
~~~

View File

@@ -0,0 +1,12 @@
Disable Custom Types
====================
{{#include ../links.md}}
The [`no_object`] feature disables support for [custom types] including:
* [_method-style_]({{rootUrl}}/rust/methods.md}}) function calls (e.g. `obj.method()`),
* [object maps] and the [`Map`] type,
* the `register_get`, `register_set` and `register_get_set` APIs for [`Engine`]

View File

@@ -0,0 +1,193 @@
`Dynamic` Parameters in Rust Functions
======================================
{{#include ../links.md}}
It is possible for Rust functions to contain parameters of type [`Dynamic`].
A [`Dynamic`] value can hold any clonable type.
```admonish question.small "Trivia"
The `push` method of an [array] is implemented as follows (minus code for [safety] protection
against [over-sized arrays][maximum size of arrays]), allowing the function to be called with
all item types.
~~~rust
// 'item: Dynamic' matches all data types
fn push(array: &mut Array, item: Dynamic) {
array.push(item);
}
~~~
```
Precedence
----------
Any parameter in a registered Rust function with a specific type has higher precedence over
[`Dynamic`], so it is important to understand which _version_ of a function will be used.
Parameter matching starts from the left to the right.
Candidate functions will be matched in order of parameter types.
Therefore, always leave [`Dynamic`] parameters (up to 16, see below) as far to the right as possible.
```rust
use rhai::{Engine, Dynamic};
// Different versions of the same function 'foo'
// will be matched in the following order.
fn foo1(x: i64, y: &str, z: bool) { }
fn foo2(x: i64, y: &str, z: Dynamic) { }
fn foo3(x: i64, y: Dynamic, z: bool) { }
fn foo4(x: i64, y: Dynamic, z: Dynamic) { }
fn foo5(x: Dynamic, y: &str, z: bool) { }
fn foo6(x: Dynamic, y: &str, z: Dynamic) { }
fn foo7(x: Dynamic, y: Dynamic, z: bool) { }
fn foo8(x: Dynamic, y: Dynamic, z: Dynamic) { }
let mut engine = Engine::new();
// Register all functions under the same name (order does not matter)
engine.register_fn("foo", foo5)
.register_fn("foo", foo7)
.register_fn("foo", foo2)
.register_fn("foo", foo8)
.register_fn("foo", foo1)
.register_fn("foo", foo3)
.register_fn("foo", foo6)
.register_fn("foo", foo4);
```
~~~admonish warning "Only the right-most 16 parameters can be `Dynamic`"
The number of parameter permutations goes up exponentially, and therefore there is a realistic limit
of 16 parameters allowed to be [`Dynamic`], counting from the _right-most side_.
For example, Rhai will not find the following function &ndash; Oh! and those 16 parameters to the right
certainly have nothing to do with it!
```rust
// The 'd' parameter counts 17th from the right!
fn weird(a: i64, d: Dynamic, x1: i64, x2: i64, x3: i64, x4: i64,
x5: i64, x6: i64, x7: i64, x8: i64,
x9: i64, x10: i64, x11: i64, x12: i64,
x13: i64, x14: i64, x15: i64, x16: i64) {
// ... do something unspeakably evil with all those parameters ...
}
```
~~~
TL;DR
-----
```admonish question "How is this implemented?"
#### Hash lookup
Since functions in Rhai can be [overloaded][function overloading], Rhai uses a single _hash_ number
to quickly lookup the actual function, based on argument types.
For each function call, a hash is calculated from:
1. the function's [namespace][function namespace], if any,
2. the function's name,
3. number of arguments (its _arity_),
4. unique ID of the type of each argument, if any.
The correct function is then obtained via a simple hash lookup.
#### Limitations
This method is _fast_, but at the expense of flexibility (such as multiple argument types that must
map to a single version). That is because each type has a different ID, and thus they calculate to
different hash numbers.
This is the reason why [generic functions](generic.md) must be expanded into concrete types.
The type ID of [`Dynamic`] is different from any other type, but it must match all types seamlessly.
Needless to say, this creates a slight problem.
#### Trying combinations
If the combined hash calculated from the actual argument type ID's is not found, then the [`Engine`]
calculates hashes for different _combinations_ of argument types and [`Dynamic`], systematically
replacing different arguments with [`Dynamic`] _starting from the right-most parameter_.
Thus, assuming a three-argument function call:
~~~rust
foo(42, "hello", true);
~~~
The following hashes will be calculated, in order.
They will be _all different_.
| Order | Hash calculation method |
| :---: | --------------------------------------------------- |
| 1 | `foo` + 3 + `i64` + `string` + `bool` |
| 2 | `foo` + 3 + `i64` + `string` + [`Dynamic`] |
| 3 | `foo` + 3 + `i64` + [`Dynamic`] + `bool` |
| 4 | `foo` + 3 + `i64` + [`Dynamic`] + [`Dynamic`] |
| 5 | `foo` + 3 + [`Dynamic`] + `string` + `bool` |
| 6 | `foo` + 3 + [`Dynamic`] + `string` + [`Dynamic`] |
| 7 | `foo` + 3 + [`Dynamic`] + [`Dynamic`] + `bool` |
| 8 | `foo` + 3 + [`Dynamic`] + [`Dynamic`] + [`Dynamic`] |
Therefore, the version with all the correct parameter types will always be found first if it exists.
At soon as a hash is found, the process stops.
Otherwise, it goes on for up to 16 arguments, or at most 65,536 tries.
That's where the 16 parameters limit comes from.
```
```admonish question "What?! It calculates 65,536 hashes for each function call???!!!"
Of course not. Don't be silly.
#### Not every function has 16 parameters
Studies have repeatedly shown that most functions accept few parameters, with the mean between
2-3 parameters per function. Functions with more than 5 parameters are rare in normal code bases.
If at all, they are usually [closures] that _capture_ lots of external variables, bumping up the
parameter count; but [closures] are always script-defined and thus all parameters are already
[`Dynamic`].
In fact, you have a bigger problem if you write such a function that you need to call regularly.
It would be far more efficient to group those parameters into [object maps].
#### Caching to the rescue
Function hashes are _cached_, so this process only happens _once_, and only up to the number of
rounds for the correct function to be found.
If not, then yes, it will calculate up to 2<sup>_n_</sup> hashes where _n_ is the number of
arguments (up to 16). But again, this will only be done _once_ for that particular
combination of argument types.
```
```admonish danger "But then... beware module functions"
The functions resolution _cache_ resides only in the [global namespace][function namespace].
This is a limitation.
Therefore, calls to functions in an [`import`]ed [module] (i.e. _qualified_ with
a [namespace][function namespace] path) do not have the benefit of a cache.
Thus, up to 2<sup>_n_</sup> hashes are calculated during _every_ function call.
This is unlikely to cause a performance issue since most functions accept only a few parameters.
```

View File

@@ -0,0 +1,94 @@
`Dynamic` Return Value
======================
{{#include ../links.md}}
Rhai supports registering functions that return [`Dynamic`].
A [`Dynamic`] value can hold any clonable type.
```rust
use rhai::{Engine, Dynamic};
// The 'Dynamic' return type allows this function to
// return values of any supported type!
fn get_info(bag: &mut PropertyBag, key: &str) -> Dynamic {
if let Some(prop_type) = bag.get_type(key) {
match prop_type {
// Use '.into()' for standard types
"string" => bag.get::<&str>(key).into(),
"int" => bag.get::<i64>(key).into(),
"bool" => bag.get::<bool>(key).into(),
:
:
// Use 'Dynamic::from' for custom types
"bag" => Dynamic::from(bag.get::<PropertyBag>(key))
}
} else {
// Return () upon error
Dynamic::UNIT
}
}
let mut engine = Engine::new();
engine.register_fn("get_info", get_info);
```
~~~admonish tip.small "Tip: Create a `Dynamic`"
To create a [`Dynamic`] value, use `Dynamic::from`.
[Standard types] in Rhai can also use `.into()`.
```rust
use rhai::Dynamic;
let obj = TestStruct::new();
let x = Dynamic::from(obj);
// '.into()' works for standard types
let x = 42_i64.into();
let y = "hello!".into();
```
~~~
~~~admonish tip.small "Tip: Alternative to fallible functions"
Instead of registering a [fallible function], it is usually more idiomatic to leverage the _dynamic_
nature of Rhai and simply return [`()`] upon error.
```rust
use rhai::{Engine, Dynamic};
// Function that may fail - return () upon failure
fn safe_divide(x: i64, y: i64) -> Dynamic {
if y == 0 {
// Return () to indicate an error if y is zero
Dynamic::UNIT
} else {
// Use '.into()' to convert standard types to 'Dynamic'
(x / y).into()
}
}
let mut engine = Engine::new();
engine.register_fn("divide", safe_divide);
// The following prints 'error!'
engine.run(r#"
let result = divide(40, 0);
if result == () {
print("error!");
} else {
print(result);
}
"#)?;
```
~~~

View File

@@ -0,0 +1,54 @@
Register a Fallible Rust Function
=================================
{{#include ../links.md}}
~~~admonish tip.side.wide "Tip: Consider `Dynamic`"
A lot of times it is not necessary to register fallible functions.
Simply have the function returns [`Dynamic`].
Upon error, return [`()`] which is idiomatic in Rhai.
See [here](dynamic-return.md) for more details.
~~~
If a function is _fallible_ (i.e. it returns a `Result<_, _>`), it can also be registered with via
`Engine::register_fn`.
The function must return `Result<T, Box<EvalAltResult>>` where `T` is any clonable type.
In other words, the error type _must_ be `Box<EvalAltResult>`. It is `Box`ed in order to reduce
the size of the `Result` type since the error path is rarely hit.
```rust
use rhai::{Engine, EvalAltResult};
// Function that may fail - the error type must be 'Box<EvalAltResult>'
fn safe_divide(x: i64, y: i64) -> Result<i64, Box<EvalAltResult>> {
if y == 0 {
// Return an error if y is zero
Err("Division by zero!".into()) // shortcut to create Box<EvalAltResult::ErrorRuntime>
} else {
Ok(x / y)
}
}
let mut engine = Engine::new();
engine.register_fn("divide", safe_divide);
if let Err(error) = engine.eval::<i64>("divide(40, 0)") {
println!("Error: {error:?}"); // prints ErrorRuntime("Division by zero detected!", (1, 1)")
}
```
~~~admonish tip.small "Tip: Create a `Box<EvalAltResult>`"
`Box<EvalAltResult>` implements `From<&str>` and `From<String>` etc.
and the error text gets converted into `Box<EvalAltResult::ErrorRuntime>`.
The error values are `Box`-ed in order to reduce memory footprint of the error path,
which should be hit rarely.
~~~

View File

@@ -0,0 +1,20 @@
Get Scripted Functions Metadata from AST
=========================================
{{#include ../links.md}}
Use [`AST::iter_functions`](https://docs.rs/rhai/latest/rhai/struct.AST.html#method.iter_functions)
to iterate through all the script-defined [functions] in an [`AST`].
`ScriptFnMetadata`
------------------
The type returned from the iterator is `ScriptFnMetadata` with the following fields:
| Field | Requires | Type | Description |
| ---------- | :----------: | :---------: | --------------------------------------------------------------------- |
| `name` | | `&str` | Name of [function] |
| `params` | | `Vec<&str>` | Number of parameters |
| `access` | | `FnAccess` | • `FnAccess::Public` (public)<br/>`FnAccess::Private` ([`private`]) |
| `comments` | [`metadata`] | `Vec<&str>` | [Doc-comments], if any, one per line |

View File

@@ -0,0 +1,151 @@
Register a Rust Function for Use in Rhai Scripts
================================================
{{#include ../links.md}}
Rhai's scripting engine is very lightweight. It gets most of its abilities from functions.
To call these functions, they need to be _registered_ via `Engine::register_fn`.
```rust
use rhai::{Dynamic, Engine, ImmutableString};
// Normal function that returns a standard type
// Remember to use 'ImmutableString' and not 'String'
fn add_len(x: i64, s: ImmutableString) -> i64 {
x + s.len()
}
// Alternatively, '&str' maps directly to 'ImmutableString'
fn add_len_count(x: i64, s: &str, c: i64) -> i64 {
x + s.len() * c
}
// Function that returns a 'Dynamic' value
fn get_any_value() -> Dynamic {
42_i64.into() // standard types can use '.into()'
}
let mut engine = Engine::new();
// Notice that all three functions are overloaded into the same name with
// different number of parameters and/or parameter types.
engine.register_fn("add", add_len)
.register_fn("add", add_len_count)
.register_fn("add", get_any_value)
.register_fn("inc", |x: i64| { // closure is also OK!
x + 1
})
.register_fn("log", |label: &str, x: i64| {
println!("{label} = {x}");
});
let result = engine.eval::<i64>(r#"add(40, "xx")"#)?;
println!("Answer: {result}"); // prints 42
let result = engine.eval::<i64>(r#"add(40, "x", 2)"#)?;
println!("Answer: {result}"); // prints 42
let result = engine.eval::<i64>("add()")?;
println!("Answer: {result}"); // prints 42
let result = engine.eval::<i64>("inc(41)")?;
println!("Answer: {result}"); // prints 42
engine.run(r#"log("value", 42)"#)?; // prints "value = 42"
```
~~~admonish tip "Tip: Use closures"
It is common for short functions to be registered via a _closure_.
```rust
┌──────┐
Rust
└──────┘
engine.register_fn("foo", |x: i64, y: i64| x * 2 + y * 3);
// ^^^ ^^^
// Usually parameter types need to be specified
engine.register_fn("bar", |x: i64| -> Result<_, Box<EvalAltResult>> { x * 2 });
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// For fallible closures, the return ERROR type may need to be specified
┌─────────────┐
Rhai script
└─────────────┘
foo(42, 100); // <- 42 * 2 + 100 * 3
```
#### Interact with external environment
An additional benefit to using closures is that they can capture external variables.
For example, capturing a type wrapped in shared mutability (e.g. `Rc<RefCell<T>>`)
allows a script to interact with the external environment through that shared type.
See also: [Control Layer]({{rootUrl}}/patterns/control.md).
```rust
┌──────┐
Rust
└──────┘
/// A type that encapsulates some behavior.
#[derive(Clone)]
struct TestStruct { ... }
impl TestSTruct {
/// Some action defined on that type.
pub fn do_foo(&self, x: i64, y: bool) {
// ... do something drastic with x and y
}
}
/// Wrapped in shared mutability: Rc<RefCell<TestStruct>>.
let shared_obj = Rc::new(RefCell::new(TestStruct::new()));
/// Clone the shared reference and move it into the closure.
let embedded_obj = shared.clone();
engine.register_fn("foo", move |x: i64, y: bool| {
// ^^^^ 'embedded_obj' is captured into the closure
embedded_obj.borrow().do_foo(x, y);
});
┌─────────────┐
Rhai script
└─────────────┘
foo(42, true); // <- equivalent to: shared_obj.borrow().do_foo(42, true);
```
~~~
~~~admonish warning "Warning: Don't touch those generic parameters"
Rhai uses an intricate system of traits (in particular `RhaiNativeFunc`) with many generic parameters to ensure
an intuitive and smooth developer experience that _just works_.
Because of this, it is not recommended to touch those generic parameters directly. These generic parameters may change
liberally in future versions of Rhai. In most situations they are automatically inferred by the compiler.
In the cases where the compiler fail to infer types when registering a _closure_, (usually with the _error_ type
of a [fallible function]), manually declare the parameter and/or return types.
```rust
// The following fails to compile because the compiler does not know
// the return _error_ type of the closure.
// It knows the return type, which is 'Result<i64, E>', but 'E' is not known.
engine.register_fn("foo", |x: i64| Ok(x));
// Don't do this...
engine.register_fn::<_, 1, false, i64, Box<EvalAltResult>>("foo", |x: i64| Ok(x));
// Do this...
engine.register_fn("foo", |x: i64| -> Result<i64, Box<EvalAltResult>> { Ok(x) });
```
~~~

View File

@@ -0,0 +1,36 @@
Register a Generic Rust Function
================================
{{#include ../links.md}}
```admonish warning.small "No monomorphization"
Due to its dynamic nature, Rhai cannot monomorphize generic functions automatically.
Monomorphization of generic functions must be performed manually.
```
Rust generic functions can be used in Rhai, but separate instances for each concrete type must be
registered separately.
This essentially _overloads_ the function with different parameter types as Rhai does not natively
support generics but Rhai does support _[function overloading]_.
The example below shows how to register multiple functions (or, in this case, multiple overloaded
versions of the same function) under the same name.
```rust
use std::fmt::Display;
use rhai::Engine;
fn show_it<T: Display>(x: &mut T) {
println!("put up a good show: {x}!");
}
let mut engine = Engine::new();
engine.register_fn("print", show_it::<i64>)
.register_fn("print", show_it::<bool>)
.register_fn("print", show_it::<ImmutableString>);
```

View File

@@ -0,0 +1,168 @@
Custom Type Property Getters and Setters
========================================
{{#include ../links.md}}
```admonish warning.side.wide "Cannot override object maps"
Property getters and setters are intended for [custom types].
Any getter or setter function registered for [object maps] is simply _ignored_.
Get/set syntax on [object maps] is interpreted as access to properties.
```
A [custom type] can also expose properties by registering `get` and/or `set` functions.
Properties can be accessed in a Rust-like syntax:
> _object_ `.` _property_
>
> _object_ `.` _property_ `=` _value_ `;`
The [_Elvis operator_](https://en.wikipedia.org/wiki/Elvis_operator) can be used to short-circuit
processing if the object itself is [`()`]:
> `// returns () if object is ()`
> _object_ `?.` _property_
>
> `// no action if object is ()`
> _object_ `?.` _property_ `=` _value_ `;`
Property getter and setter functions are called behind the scene.
They each take a `&mut` reference to the first parameter.
Getters and setters are disabled under the [`no_object`] feature.
<section></section>
| `Engine` API | Function signature(s)<br/>(`T: Clone` = custom type,<br/>`V: Clone` = data type) | Can mutate `T`? |
| ------------------ | -------------------------------------------------------------------------------- | :----------------------------: |
| `register_get` | `Fn(&mut T) -> V` | yes, but not advised |
| `register_set` | `Fn(&mut T, V)` | yes |
| `register_get_set` | getter: `Fn(&mut T) -> V`</br>setter: `Fn(&mut T, V)` | yes, but not advised in getter |
```admonish danger.small "No support for references"
Rhai does NOT support normal references (i.e. `&T`) as parameters.
All references must be mutable (i.e. `&mut T`).
```
```admonish warning.small "Getters must be pure"
By convention, property getters are assumed to be _pure_, meaning that they are not supposed to
mutate the [custom type], although there is nothing that prevents this mutation in Rust.
Even though a property getter function also takes `&mut` as the first parameter, Rhai assumes that
no data is changed when the function is called.
```
Examples
--------
```rust
#[derive(Debug, Clone)]
struct TestStruct {
field: String
}
impl TestStruct {
// Remember &mut must be used even for getters.
fn get_field(&mut self) -> String {
// Property getters are assumed to be PURE, meaning they are
// not supposed to mutate any data.
self.field.clone()
}
fn set_field(&mut self, new_val: String) {
self.field = new_val;
}
fn new() -> Self {
Self { field: "hello, world!".to_string() }
}
}
let mut engine = Engine::new();
engine.register_type::<TestStruct>()
.register_get_set("xyz", TestStruct::get_field, TestStruct::set_field)
.register_fn("new_ts", TestStruct::new);
let result = engine.eval::<String>(
r#"
let a = new_ts();
a.xyz = "42";
a.xyz
"#)?;
println!("Answer: {result}"); // prints 42
```
Fallback to Indexer
-------------------
```admonish note.side "See also"
See [this section](indexer-prop-fallback.md) for details on an [indexer]
acting as fallback to properties.
```
If the getter/setter of a particular property is not defined, but an [indexer] is defined on the
[custom type] with [string] index, then the corresponding [indexer] will be called with the name of
the property as the index value.
In other words, [indexers] act as a _fallback_ to property getters/setters.
```rust
a.foo // if property getter for 'foo' doesn't exist...
a["foo"] // an indexer (if any) is tried
```
```admonish tip.small "Tip: Implement a property bag"
This feature makes it very easy for [custom types] to act as _property bags_
(similar to an [object map]) which can add/remove properties at will.
```
Chaining Updates
----------------
It is possible to _chain_ property accesses and/or indexing (via [indexers]) together to modify a
particular property value at the end of the chain.
Rhai detects such modifications and updates the changed values all the way back up the chain.
In the end, the syntax works as expected by intuition, automatically and without special attention.
```rust
// Assume a deeply-nested object...
let root = get_new_container_object();
root.prop1.sub["hello"].list[0].value = 42;
// The above is equivalent to:
// First getting all the intermediate values...
let prop1_value = root.prop1; // via property getter
let sub_value = prop1_value.sub; // via property getter
let sub_value_item = sub_value["hello"]; // via index getter
let list_value = sub_value_item.list; // via property getter
let list_item = list_value[0]; // via index getter
list_item.value = 42; // modify property value deep down the chain
// Propagate the changes back up the chain...
list_value[0] = list_item; // via index setter
sub_value_item.list = list_value; // via property setter
sub_value["hello"] = sub_value_item; // via index setter
prop1_value.sub = sub_value; // via property setter
root.prop1 = prop1_value; // via property setter
// The below prints 42...
print(root.prop1.sub["hello"].list[0].value);
```

View File

@@ -0,0 +1,76 @@
The `ImmutableString` Type
==========================
{{#include ../links.md}}
~~~admonish question.side "Why `SmartString`?"
[`SmartString`] is used because many strings in scripts are short (fewer than 24 ASCII characters).
~~~
All [strings] in Rhai are implemented as `ImmutableString`, which is an alias to
`Rc<SmartString>` (or `Arc<SmartString>` under the [`sync`] feature).
An `ImmutableString` is immutable (i.e. never changes) and therefore can be shared among many users.
Cloning an `ImmutableString` is cheap since it only copies an immutable reference.
Modifying an `ImmutableString` causes it first to be cloned, and then the modification made to the copy.
Therefore, direct string modifications are expensive.
Avoid `String` Parameters
-------------------------
`ImmutableString` should be used in place of `String` for function parameters because using `String`
is very inefficient (the argument is cloned during every function call).
A alternative is to use `&str` which de-sugars to `ImmutableString`.
A function with the first parameter being `&mut String` does not match a string argument passed to it,
which has type `ImmutableString`. In fact, `&mut String` is treated as an opaque [custom type].
```rust
fn slow(s: String) -> i64 { ... } // string is cloned each call
fn fast1(s: ImmutableString) -> i64 { ... } // cloning 'ImmutableString' is cheap
fn fast2(s: &str) -> i64 { ... } // de-sugars to above
fn bad(s: &mut String) { ... } // '&mut String' will not match string values
```
Differences from Rust Strings
-----------------------------
Internally Rhai strings are stored as UTF-8 just like Rust (they _are_ Rust `String`s!),
but nevertheless there are major differences.
In Rhai a [string] is semantically the same as an array of Unicode characters and can be directly
indexed (unlike Rust).
This is similar to most other languages (e.g. JavaScript, C#) where strings are stored internally
not as UTF-8 but as arrays of UCS-16 or UCS-32.
Individual characters within a Rhai string can also be replaced just as if the string is an array of
Unicode characters.
In Rhai, there are also no separate concepts of `String` and `&str` (a string slice) as in Rust.
```admonish warning "Performance considerations"
Although Rhai exposes a [string] as a simple array of [characters] which can be directly indexed to
get at a particular [character], such convenient syntax is an _illusion_.
Internally the [string] is still stored in UTF-8 (native Rust `String`s).
All indexing operations actually require walking through the entire UTF-8 string to find the offset
of the particular [character] position, and therefore is _much_ slower than the simple array
indexing for other scripting languages.
This implementation detail is hidden from the user but has a performance implication.
Avoid large scale [character]-based processing of [strings]; instead, build an actual [array] of
[characters] (via the `split()` method) which can then be manipulated efficiently.
```

View File

@@ -0,0 +1,11 @@
Extend Rhai with Rust
=====================
{{#title Extend Rhai with Rust}}
{{#include ../links.md}}
Most features and functionalities required by a Rhai script should actually be coded in Rust,
which leverages the superior native run-time speed.
This section discusses how to extend Rhai with functionalities written in Rust.

View File

@@ -0,0 +1,101 @@
Indexer as Property Access Fallback
===================================
{{#include ../links.md}}
```admonish tip.side "Tip: Property bag"
Such an [indexer] allows easy creation of _property bags_ (similar to [object maps])
which can dynamically add/remove properties.
```
An [indexer] taking a [string] index is a special case &ndash; it acts as a _fallback_ to property
[getters/setters].
During a property access, if the appropriate property [getter/setter][getters/setters] is not
defined, an [indexer] is called and passed the string name of the property.
This is also extremely useful as a short-hand for [indexers], when the [string] keys conform to
property name syntax.
```rust
// Assume 'obj' has an indexer defined with string parameters...
// Let's create a new key...
obj.hello_world = 42;
// The above is equivalent to this:
obj["hello_world"] = 42;
// You can write this...
let x = obj["hello_world"];
// but it is easier with this...
let x = obj.hello_world;
```
~~~admonish tip.small "Tip: Swizzling"
Since an [indexer] can serve as a _fallback_ to [property access][getters/setters],
it is possible to implement [swizzling](https://en.wikipedia.org/wiki/Swizzling_(computer_graphics))
of properties for use with vector-like [custom types].
Such an [indexer] defined on a [custom type] (for instance, `Float4`) can inspect the property name,
construct a proper return value based on the swizzle pattern, and return it.
```rust
// Assume 'v' is a 'Float4'
let r = v.w; // -> v.w
let r = v.xx; // -> Float2::new(v.x, v.x)
let r = v.yxz; // -> Float3::new(v.y, v.x, v.z)
let r = v.xxzw; // -> Float4::new(v.x, v.x, v.z, v.w)
let r = v.yyzzxx; // error: property 'yyzzxx' not found
```
~~~
Caveat &ndash; Reverse is NOT True
----------------------------------
The reverse, however, is not true &ndash; when an [indexer] fails or doesn't exist, the corresponding
property [getter/setter][getters/setters], if any, is not called.
```rust
type MyType = HashMap<String, i64>;
let mut engine = Engine::new();
// Define custom type, property getter and string indexers
engine.register_type::<MyType>()
.register_fn("new_ts", || {
let mut obj = MyType::new();
obj.insert("foo".to_string(), 1);
obj.insert("bar".to_string(), 42);
obj.insert("baz".to_string(), 123);
obj
})
// Property 'hello'
.register_get("hello", |obj: &mut MyType| obj.len() as i64)
// Index getter/setter
.register_indexer_get(|obj: &mut MyType, prop: &str| -> Result<i64, Box<EvalAltResult>>
obj.get(index).cloned().ok_or_else(|| "not found".into())
).register_indexer_set(|obj: &mut MyType, prop: &str, value: i64|
obj.insert(prop.to_string(), value)
);
engine.run("let ts = new_ts(); print(ts.foo);");
// ^^^^^^
// Calls ts["foo"] - getter for 'foo' does not exist
engine.run("let ts = new_ts(); print(ts.bar);");
// ^^^^^^
// Calls ts["bar"] - getter for 'bar' does not exist
engine.run("let ts = new_ts(); ts.baz = 999;");
// ^^^^^^^^^^^^
// Calls ts["baz"] = 999 - setter for 'baz' does not exist
engine.run(r#"let ts = new_ts(); print(ts["hello"]);"#);
// ^^^^^^^^^^^
// Error: Property getter for 'hello' not a fallback for indexer
```

View File

@@ -0,0 +1,197 @@
Custom Type Indexers
====================
{{#include ../links.md}}
A [custom type] can also expose an _indexer_ by registering an indexer function.
A [custom type] with an indexer function defined can use the bracket notation to get/set a property
value at a particular index:
> _object_ `[` _index_ `]`
>
> _object_ `[` _index_ `]` `=` _value_ `;`
The [_Elvis notation_][elvis] is similar except that it returns [`()`] if the object itself is [`()`].
> `// returns () if object is ()`
> _object_ `?[` _index_ `]`
>
> `// no action if object is ()`
> _object_ `?[` _index_ `]` `=` _value_ `;`
Like property [getters/setters], indexers take a `&mut` reference to the first parameter.
They also take an additional parameter of any type that serves as the _index_ within brackets.
Indexers are disabled when the [`no_index`] and [`no_object`] features are used together.
| `Engine` API | Function signature(s)<br/>(`T: Clone` = custom type,<br/>`X: Clone` = index type,<br/>`V: Clone` = data type) | Can mutate `T`? |
| -------------------------- | ------------------------------------------------------------------------------------------------------------- | :----------------------------: |
| `register_indexer_get` | `Fn(&mut T, X) -> V` | yes, but not advised |
| `register_indexer_set` | `Fn(&mut T, X, V)` | yes |
| `register_indexer_get_set` | getter: `Fn(&mut T, X) -> V`<br/>setter: `Fn(&mut T, X, V)` | yes, but not advised in getter |
```admonish danger.small "No support for references"
Rhai does NOT support normal references (i.e. `&T`) as parameters.
All references must be mutable (i.e. `&mut T`).
```
```admonish warning.small "Getters must be pure"
By convention, index getters are not supposed to mutate the [custom type],
although there is nothing that prevents this mutation.
```
~~~admonish tip.small "Tip: `EvalAltResult::ErrorIndexNotFound`"
For [fallible][fallible function] indexers, it is customary to return
`EvalAltResult::ErrorIndexNotFound` when called with an invalid index value.
~~~
Cannot Override Arrays, BLOB's, Object Maps, Strings and Integers
-----------------------------------------------------------------
```admonish failure.side "Plugins"
They can be defined in a [plugin module], but will be ignored.
```
For efficiency reasons, indexers **cannot** be used to overload (i.e. override)
built-in indexing operations for [arrays], [object maps], [strings] and integers
(acting as [bit-field] operation).
The following types have built-in indexer implementations that are fast and efficient.
<section></section>
| Type | Index type | Return type | Description |
| ----------------------------------------- | :---------------------------------------: | :---------: | ---------------------------------------------------------------------------- |
| [`Array`] | `INT` | [`Dynamic`] | access a particular element inside the [array] |
| [`Blob`] | `INT` | `INT` | access a particular byte value inside the [BLOB] |
| [`Map`] | [`ImmutableString`],<br/>`String`, `&str` | [`Dynamic`] | access a particular property inside the [object map] |
| [`ImmutableString`],<br/>`String`, `&str` | `INT` | [character] | access a particular [character] inside the [string] |
| `INT` | `INT` | boolean | access a particular bit inside the integer number as a [bit-field] |
| `INT` | [range] | `INT` | access a particular range of bits inside the integer number as a [bit-field] |
```admonish warning.small "Do not overload indexers for built-in standard types"
In general, it is a bad idea to overload indexers for any of the [standard types] supported
internally by Rhai, since built-in indexers may be added in future versions.
```
Examples
--------
```rust
#[derive(Debug, Clone)]
struct TestStruct {
fields: Vec<i64>
}
impl TestStruct {
// Remember &mut must be used even for getters
fn get_field(&mut self, index: String) -> i64 {
self.fields[index.len()]
}
fn set_field(&mut self, index: String, value: i64) {
self.fields[index.len()] = value
}
fn new() -> Self {
Self { fields: vec![1, 2, 3, 4, 5] }
}
}
let mut engine = Engine::new();
engine.register_type::<TestStruct>()
.register_fn("new_ts", TestStruct::new)
// Short-hand: .register_indexer_get_set(TestStruct::get_field, TestStruct::set_field);
.register_indexer_get(TestStruct::get_field)
.register_indexer_set(TestStruct::set_field);
let result = engine.eval::<i64>(
r#"
let a = new_ts();
a["xyz"] = 42; // these indexers use strings
a["xyz"] // as the index type
"#)?;
println!("Answer: {result}"); // prints 42
```
Convention for Negative Index
-----------------------------
If the indexer takes a signed integer as an index (e.g. the standard `INT` type), care should be
taken to handle _negative_ values passed as the index.
It is a standard API _convention_ for Rhai to assume that an index position counts _backwards_ from
the _end_ if it is negative.
`-1` as an index usually refers to the _last_ item, `-2` the second to last item, and so on.
Therefore, negative index values go from `-1` (last item) to `-length` (first item).
A typical implementation for negative index values is:
```rust
// The following assumes:
// 'index' is 'INT', 'items: usize' is the number of elements
let actual_index = if index < 0 {
index.checked_abs().map_or(0, |n| items - (n as usize).min(items))
} else {
index as usize
};
```
The _end_ of a data type can be interpreted creatively. For example, in an integer used as a
[bit-field], the _start_ is the _least-significant-bit_ (LSB) while the `end` is the
_most-significant-bit_ (MSB).
Convention for Range Index
--------------------------
```admonish tip.side.wide "Tip: Negative values"
By convention, negative values are _not_ interpreted specially in indexers for [ranges].
```
It is very common for [ranges] to be used as indexer parameters via the types
`std::ops::Range<INT>` (exclusive) and `std::ops::RangeInclusive<INT>` (inclusive).
One complication is that two versions of the same indexer must be defined to support _exclusive_
and _inclusive_ [ranges] respectively.
```rust
use std::ops::{Range, RangeInclusive};
let mut engine = Engine::new();
engine
/// Version of indexer that accepts an exclusive range
.register_indexer_get_set(
|obj: &mut TestStruct, range: Range<i64>| -> bool { ... },
|obj: &mut TestStruct, range: Range<i64>, value: bool| { ... },
)
/// Version of indexer that accepts an inclusive range
.register_indexer_get_set(
|obj: &mut TestStruct, range: RangeInclusive<i64>| -> bool { ... },
|obj: &mut TestStruct, range: RangeInclusive<i64>, value: bool| { ... },
);
engine.run(
"
let obj = new_ts();
let x = obj[0..12]; // use exclusive range
obj[0..=11] = !x; // use inclusive range
")?;
```

View File

@@ -0,0 +1,180 @@
Call Method as Function
=======================
{{#include ../links.md}}
Method-Call Style vs. Function-Call Style
-----------------------------------------
### Method-call syntax
> _object_ `.` _function_ `(` _parameter_`,` ... `,` _parameter_`)`
~~~admonish warning.small "_Method-call_ style not supported under [`no_object`]"
```rust
// Below is a syntax error under 'no_object'.
engine.run("let x = [42]; x.clear();")?;
// ^ cannot call method-style
```
~~~
### Function-call syntax
> _function_ `(` _object_`,` _parameter_`,` ... `,` _parameter_`)`
### Equivalence
```admonish note.side
This design is similar to Rust.
```
Internally, methods on a [custom type] is _the same_ as a function taking a `&mut` first argument of
the object's type.
Therefore, methods and functions can be called interchangeably.
```rust
impl TestStruct {
fn foo(&mut self) -> i64 {
self.field
}
}
engine.register_fn("foo", TestStruct::foo);
let result = engine.eval::<i64>(
"
let x = new_ts();
foo(x); // normal call to 'foo'
x.foo() // 'foo' can also be called like a method on 'x'
")?;
println!("result: {result}"); // prints 1
```
First `&mut` Parameter
----------------------
The opposite direction also works &mdash; methods in a Rust [custom type] registered with the
[`Engine`] can be called just like a regular function. In fact, like Rust, object methods are
registered as regular [functions] in Rhai that take a first `&mut` parameter.
Unlike functions defined in script (for which all arguments are passed by _value_),
native Rust functions may mutate the first `&mut` argument.
Sometimes, however, there are more subtle differences. Methods called in normal function-call style
may end up not muting the object afterall &mdash; see the example below.
Custom types, [properties][getters/setters], [indexers] and methods are disabled under the
[`no_object`] feature.
```rust
let a = new_ts(); // constructor function
a.field = 500; // property setter
a.update(); // method call, 'a' can be modified
update(a); // <- this de-sugars to 'a.update()'
// 'a' can be modified and is not a copy
let array = [ a ];
update(array[0]); // <- 'array[0]' is an expression returning a calculated value,
// a transient (i.e. a copy), so this statement has no effect
// except waste time cloning 'a'
array[0].update(); // <- call in method-call style will update 'a'
```
```admonish danger.small "No support for references"
Rhai does NOT support normal references (i.e. `&T`) as parameters.
All references must be mutable (i.e. `&mut T`).
```
Number of Parameters in Methods
-------------------------------
Native Rust methods registered with an [`Engine`] take _one additional parameter_ more than
an equivalent method coded in script, where the object is accessed via the `this` pointer instead.
The following table illustrates the differences:
| Function type | No. of parameters | Object reference | Function signature |
| :-----------: | :---------------: | :----------------------: | :---------------------------: |
| Native Rust | _N_ + 1 | first `&mut T` parameter | `Fn(obj: &mut T, x: U, y: V)` |
| Rhai script | _N_ | `this` | `Fn(x: U, y: V)` |
`&mut` is Efficient, Except for `&mut ImmutableString`
------------------------------------------------------
Using a `&mut` first parameter is highly encouraged when using types that are expensive to clone,
even when the intention is not to mutate that argument, because it avoids cloning that argument value.
Even when a function is never intended to be a method &ndash; for example an operator,
it is still sometimes beneficial to make it method-like (i.e. with a first `&mut` parameter)
if the first parameter is not modified.
For types that are expensive to clone (remember, all function calls are passed cloned
copies of argument values), this may result in a significant performance boost.
For primary types that are cheap to clone (e.g. those that implement `Copy`), including `ImmutableString`,
this is not necessary.
```rust
// This is a type that is very expensive to clone.
#[derive(Debug, Clone)]
struct VeryComplexType { ... }
// Calculate some value by adding 'VeryComplexType' with an integer number.
fn do_add(obj: &VeryComplexType, offset: i64) -> i64 {
...
}
engine.register_type::<VeryComplexType>()
.register_fn("+", add_pure /* or add_method*/);
// Very expensive to call, as the 'VeryComplexType' is cloned before each call.
fn add_pure(obj: VeryComplexType, offset: i64) -> i64 {
do_add(obj, offset)
}
// Efficient to call, as only a reference to the 'VeryComplexType' is passed.
fn add_method(obj: &mut VeryComplexType, offset: i64) -> i64 {
do_add(obj, offset)
}
```
Data Race Considerations
------------------------
```admonish note.side "Data races"
Data races are not possible in Rhai under the [`no_closure`] feature because no sharing ever occurs.
```
Because methods always take a mutable reference as the first argument, even it the value is never changed,
care must be taken when using _shared_ values with methods.
Usually data races are not possible in Rhai because, for each function call, there is ever only one
value that is mutable &ndash; the first argument of a method. All other arguments are cloned.
It is possible, however, to create a data race with a _shared_ value, when the same value is
_captured_ in a [closure] and then used again as the _object_ of calling that [closure]!
```rust
let x = 20;
x.is_shared() == false; // 'x' is not shared, so no data race is possible
let f = |a| this += x + a; // 'x' is captured in this closure
x.is_shared() == true; // now 'x' is shared
x.call(f, 2); // <- error: data race detected on 'x'
```

View File

@@ -0,0 +1,58 @@
Methods
=======
{{#include ../links.md}}
Methods of [custom types] are registered via `Engine::register_fn`.
```rust
use rhai::{Engine, EvalAltResult};
#[derive(Debug, Clone)]
struct TestStruct {
field: i64
}
impl TestStruct {
fn new() -> Self {
Self { field: 1 }
}
fn update(&mut self, x: i64) { // methods take &mut as first parameter
self.field += x;
}
}
let mut engine = Engine::new();
// Most Engine APIs can be chained up.
engine.register_type_with_name::<TestStruct>("TestStruct")
.register_fn("new_ts", TestStruct::new)
.register_fn("update", TestStruct::update);
// Cast result back to custom type.
let result = engine.eval::<TestStruct>(
"
let x = new_ts(); // calls 'TestStruct::new'
x.update(41); // calls 'TestStruct::update'
x // 'x' holds a 'TestStruct'
")?;
println!("result: {}", result.field); // prints 42
```
First Parameter Must be `&mut`
------------------------------
_Methods_ of [custom types] take a `&mut` first parameter to that type, so that invoking methods can
always update it.
All other parameters in Rhai are passed by value (i.e. clones).
```admonish danger.small "No support for references"
Rhai does NOT support normal references (i.e. `&T`) as parameters.
All references must be mutable (i.e. `&mut T`).
```

View File

@@ -0,0 +1,88 @@
Create a Module from an AST
===========================
{{#include ../../links.md}}
`Module::eval_ast_as_new`
-------------------------
```admonish info.side.wide "Encapsulated environment"
`Module::eval_ast_as_new` encapsulates the entire [`AST`] into each function call, merging the
[module namespace][function namespace] with the [global namespace][function namespace].
Therefore, [functions] defined within the same [module] script can cross-call each other.
```
```admonish info.side.wide "See also"
See [_Export Variables, Functions and Sub-Modules from Script_][`export`] for details on how to prepare
a Rhai script for this purpose as well as to control which [functions]/[variables] to export.
```
A [module] can be created from a single script (or pre-compiled [`AST`]) containing global
[variables], [functions] and [sub-modules][module] via `Module::eval_ast_as_new`.
When given an [`AST`], it is first evaluated (usually to [import][`import`] [modules] and set up global
[constants] used by [functions]), then the following items are exposed as members of the new [module]:
* global [variables] and [constants] &ndash; all [variables] and [constants] exported via the
[`export`] statement (those not exported remain hidden),
* [functions] not specifically marked [`private`],
* imported [modules] that remain in the [`Scope`] at the end of a script run become sub-modules.
Examples
--------
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
use rhai::{Engine, Module};
let engine = Engine::new();
// Compile a script into an 'AST'
let ast = engine.compile(
r#"
// Functions become module functions
fn calc(x) {
x + add_len(x, 1) // functions within the same module
// can always cross-call each other!
}
fn add_len(x, y) {
x + y.len
}
// Imported modules become sub-modules
import "another module" as extra;
// Variables defined at global level can become module variables
const x = 123;
let foo = 41;
let hello;
// Variable values become constant module variable values
foo = calc(foo);
hello = `hello, ${foo} worlds!`;
// Finally, export the variables and modules
export x as abc; // aliased variable name
export foo;
export hello;
"#)?;
// Convert the 'AST' into a module, using the 'Engine' to evaluate it first
// A copy of the entire 'AST' is encapsulated into each function,
// allowing functions in the module script to cross-call each other.
let module = Module::eval_ast_as_new(Scope::new(), &ast, &engine)?;
// 'module' now contains:
// - sub-module: 'extra'
// - functions: 'calc', 'add_len'
// - constants: 'abc' (renamed from 'x'), 'foo', 'hello'
```

View File

@@ -0,0 +1,24 @@
Create a Module in Rust
=======================
{{#include ../../links.md}}
The Easy Way &ndash; Plugin
---------------------------
By far the simplest way to create a [module] is via a [plugin module]
which converts a normal Rust module into a Rhai [module] via procedural macros.
The Hard Way &ndash; `Module` API
---------------------------------
Manually creating a [module] is possible via the [`Module`] public API, which is volatile and may
change from time to time.
~~~admonish info.small "`Module` public API"
For the complete [`Module`] public API, refer to the
[documentation](https://docs.rs/rhai/{{version}}/rhai/struct.Module.html) online.
~~~

View File

@@ -0,0 +1,30 @@
Modules
=======
{{#include ../../links.md}}
Rhai allows organizing functionalities ([functions], both Rust-based and scripted, and
[variables]) into independent _modules_.
A module has the type `rhai::Module` and holds a collection of [functions], [variables], [type
iterators] and sub-modules.
It may contain entirely Rust functions, or it may encapsulate a Rhai script together with
all the [functions] and [variables] defined by that script.
Other scripts then load this module and use the [functions] and [variables] [exported][`export`].
Alternatively, modules can be registered directly into an [`Engine`] and made available to scripts,
either globally or under individual static module [_namespaces_][function namespaces].
Modules can be disabled via the [`no_module`] feature.
Usage Patterns
--------------
| Usage | API | Lookup | Sub-modules? | Variables? |
| -------------- | :-------------------------------: | :----------------------: | :----------: | :--------: |
| Global module | `Engine:: register_global_module` | simple name | ignored | yes |
| Static module | `Engine:: register_static_module` | namespace-qualified name | yes | yes |
| Dynamic module | [`import`] statement | namespace-qualified name | yes | yes |

View File

@@ -0,0 +1,10 @@
Built-in Module Resolvers
=========================
{{#include ../../../links.md}}
There are a number of standard [module resolvers] built into Rhai, the default being the
[`FileModuleResolver`](file.md) which simply loads a script file based on the path (with `.rhai`
extension attached) and execute it to form a [module].
Built-in [module resolvers] are grouped under the `rhai::module_resolvers` module.

View File

@@ -0,0 +1,11 @@
`ModuleResolversCollection`
===========================
{{#include ../../../links.md}}
A collection of [module resolvers].
[Modules] are resolved from each resolver in sequential order.
This is useful when multiple types of [modules] are needed simultaneously.

View File

@@ -0,0 +1,95 @@
Implement a Custom Module Resolver
==================================
{{#include ../../../links.md}}
For many applications in which Rhai is embedded, it is necessary to customize the way that [modules]
are resolved. For instance, [modules] may need to be loaded from script texts stored in a database,
not in the file system.
A module resolver must implement the [`ModuleResolver`][traits] trait, which contains only one
required function: `resolve`.
When Rhai prepares to load a module, `ModuleResolver::resolve` is called with the name
of the _module path_ (i.e. the path specified in the [`import`] statement).
```admonish success
Upon success, it should return a shared [module] wrapped by `Rc` (or `Arc` under [`sync`]).
The module resolver should call `Module::build_index` on the target [module] before returning it.
* This method flattens the entire module tree and _indexes_ it for fast function name resolution.
* If the module is already indexed, calling this method has no effect.
```
```admonish failure
* If the path does not resolve to a valid module, return `EvalAltResult::ErrorModuleNotFound`.
* If the module failed to load, return `EvalAltResult::ErrorInModule`.
```
Example of a Custom Module Resolver
-----------------------------------
```rust
use rhai::{ModuleResolver, Module, Engine, EvalAltResult};
// Define a custom module resolver.
struct MyModuleResolver {}
// Implement the 'ModuleResolver' trait.
impl ModuleResolver for MyModuleResolver {
// Only required function.
fn resolve(
&self,
engine: &Engine, // reference to the current 'Engine'
source_path: Option<&str>, // path of the parent module
path: &str, // the module path
pos: Position, // position of the 'import' statement
) -> Result<Rc<Module>, Box<EvalAltResult>> {
// Check module path.
if is_valid_module_path(path) {
// Load the custom module
match load_secret_module(path) {
Ok(my_module) => {
my_module.build_index(); // index it
Rc::new(my_module) // make it shared
},
// Return 'EvalAltResult::ErrorInModule' upon loading error
Err(err) => Err(EvalAltResult::ErrorInModule(path.into(), Box::new(err), pos).into())
}
} else {
// Return 'EvalAltResult::ErrorModuleNotFound' if the path is invalid
Err(EvalAltResult::ErrorModuleNotFound(path.into(), pos).into())
}
}
}
let mut engine = Engine::new();
// Set the custom module resolver into the 'Engine'.
engine.set_module_resolver(MyModuleResolver {});
engine.run(
r#"
import "hello" as foo; // this 'import' statement will call
// 'MyModuleResolver::resolve' with "hello" as 'path'
foo:bar();
"#)?;
```
Advanced &ndash; `ModuleResolver::resolve_ast`
----------------------------------------------
There is another function in the [`ModuleResolver`][traits] trait, `resolve_ast`, which is a
low-level API intended for advanced usage scenarios.
`ModuleResolver::resolve_ast` has a default implementation that simply returns `None`,
which indicates that this API is not supported by the [module resolver].
Any [module resolver] that serves [modules] based on Rhai scripts should implement
`ModuleResolver::resolve_ast`. When called, the compiled [`AST`] of the script should be returned.
`ModuleResolver::resolve_ast` should not return an error if `ModuleResolver::resolve` will not.
On the other hand, the same error should be returned if `ModuleResolver::resolve` will return one.

View File

@@ -0,0 +1,11 @@
`DummyModuleResolver`
=====================
{{#include ../../../links.md}}
```admonish abstract.small "Default"
`DummyModuleResolver` is the default for [`no_std`] or [`Engine::new_raw`][raw `Engine`].
```
This [module resolver] acts as a _dummy_ and fails all module resolution calls.

View File

@@ -0,0 +1,64 @@
`DylibModuleResolver`
=====================
{{#include ../../../links.md}}
~~~admonish warning.small "Requires external crate `rhai-dylib`"
`DylibModuleResolver` resides in the [`rhai-dylib`] crate which must be specified
as a dependency:
```toml
[dependencies]
rhai-dylib = { version = "0.1" }
```
~~~
```admonish danger.small "Linux or Windows only"
[`rhai-dylib`] currently supports only Linux and Windows.
```
Parallel to how the [`FileModuleResolver`](file.md) works, `DylibModuleResolver` loads external
native Rust [modules] from compiled _dynamic shared libraries_ (e.g. `.so` in Linux and `.dll` in
Windows).
Therefore, [`FileModuleResolver`](file.md`) loads Rhai script files while `DylibModuleResolver`
loads native Rust shared libraries. It is very common to have the two work together.
Example
-------
```rust
use rhai::{Engine, Module};
use rhai::module_resolvers::{FileModuleResolver, ModuleResolversCollection};
use rhai_dylib::module_resolvers::DylibModuleResolver;
let mut engine = Engine::new();
// Use a module resolvers collection
let mut resolvers = ModuleResolversCollection::new();
// First search for script files in the file system
resolvers += FileModuleResolver::new();
// Then search for shared-library plugins in the file system
resolvers += DylibModuleResolver::new();
// Set the module resolver into the engine
engine.set_module_resolver(resolvers);
┌─────────────┐
Rhai Script
└─────────────┘
// If there is 'path/to/my_module.rhai', load it.
// Otherwise, check for 'path/to/my_module.so' on Linux
// ('path/to/my_module.dll' on Windows).
import "path/to/my_module" as m;
m::greet();
```

View File

@@ -0,0 +1,183 @@
`FileModuleResolver`
====================
{{#include ../../../links.md}}
```admonish abstract.small "Default"
`FileModuleResolver` is the default for [`Engine::new`][`Engine`].
```
The _default_ [module] resolution service, not available for [`no_std`] or [WASM] builds.
Loads a script file (based off the current directory or a specified one) with `.rhai` extension.
Function Namespace
-------------------
All functions in the [_global_ namespace][function namespace], plus all those defined in the same
[module], are _merged_ into a _unified_ [namespace][function namespace].
All [modules] imported at _global_ level via [`import`] statements become sub-[modules],
which are also available to [functions] defined within the same script file.
Base Directory
--------------
```admonish tip.side.wide "Tip: Default"
If the base directory is not set, then relative paths are based off the directory of the loading script.
This allows scripts to simply cross-load each other.
```
_Relative_ paths are resolved relative to a _root_ directory, which is usually the base directory.
The base directory can be set via `FileModuleResolver::new_with_path` or
`FileModuleResolver::set_base_path`.
Custom [`Scope`]
----------------
```admonish tip.side.wide "Tip"
This [`Scope`] can conveniently hold global [constants] etc.
```
The `set_scope` method adds an optional [`Scope`] which will be used to [optimize][script optimization] [module] scripts.
Caching
-------
```admonish tip.side.wide "Tip: Enable/disable caching"
Use `enable_cache` to enable/disable the cache.
```
By default, [modules] are also _cached_ so a script file is only evaluated _once_, even when
repeatedly imported.
Unix Shebangs
-------------
On Unix-like systems, the _shebang_ (`#!`) is used at the very beginning of a script file to mark a
script with an interpreter (for Rhai this would be [`rhai-run`]({{rootUrl}}/start/bin.md)).
If a script file starts with `#!`, the entire first line is skipped.
Because of this, Rhai scripts with shebangs at the beginning need no special processing.
```js
#!/home/to/me/bin/rhai-run
// This is a Rhai script
let answer = 42;
print(`The answer is: ${answer}`);
```
Example
-------
```rust
┌────────────────┐
│ my_module.rhai │
└────────────────┘
// This function overrides any in the main script.
private fn inner_message() { "hello! from module!" }
fn greet() {
print(inner_message()); // call function in module script
}
fn greet_main() {
print(main_message()); // call function not in module script
}
┌───────────┐
│ main.rhai │
└───────────┘
// This function is overridden by the module script.
fn inner_message() { "hi! from main!" }
// This function is found by the module script.
fn main_message() { "main here!" }
import "my_module" as m;
m::greet(); // prints "hello! from module!"
m::greet_main(); // prints "main here!"
```
Simulate Virtual Functions
--------------------------
When calling a namespace-qualified [function] defined within a [module], other [functions] defined
within the same module override any similar-named [functions] (with the same number of parameters)
defined in the [global namespace][function namespace].
This is to ensure that a [module] acts as a self-contained unit and [functions] defined in the
calling script do not override [module] code.
In some situations, however, it is actually beneficial to do it in reverse: have [module] [functions] call
[functions] defined in the calling script (i.e. in the [global namespace][function namespace]) if
they exist, and only call those defined in the [module] if none are found.
One such situation is the need to provide a _default implementation_ to a simulated _virtual_ function:
```rust
┌────────────────┐
│ my_module.rhai │
└────────────────┘
// Do not do this (it will override the main script):
// fn message() { "hello! from module!" }
// This function acts as the default implementation.
private fn default_message() { "hello! from module!" }
// This function depends on a 'virtual' function 'message'
// which is not defined in the module script.
fn greet() {
if is_def_fn("message", 0) { // 'is_def_fn' detects if 'message' is defined.
print(message());
} else {
print(default_message());
}
}
┌───────────┐
│ main.rhai │
└───────────┘
// The main script defines 'message' which is needed by the module script.
fn message() { "hi! from main!" }
import "my_module" as m;
m::greet(); // prints "hi! from main!"
┌────────────┐
│ main2.rhai │
└────────────┘
// The main script does not define 'message' which is needed by the module script.
import "my_module" as m;
m::greet(); // prints "hello! from module!"
```

View File

@@ -0,0 +1,35 @@
Module Resolvers
================
{{#include ../../../links.md}}
~~~admonish info.side "`import`"
See the section on [_Importing Modules_][`import`] for more details.
~~~
When encountering an [`import`] statement, Rhai attempts to _resolve_ the [module] based on the path string.
_Module Resolvers_ are service types that implement the [`ModuleResolver`][traits] trait.
Set into `Engine`
-----------------
An [`Engine`]'s module resolver is set via a call to `Engine::set_module_resolver`:
```rust
use rhai::module_resolvers::{DummyModuleResolver, StaticModuleResolver};
// Create a module resolver
let resolver = StaticModuleResolver::new();
// Register functions into 'resolver'...
// Use the module resolver
engine.set_module_resolver(resolver);
// Effectively disable 'import' statements by setting module resolver to
// the 'DummyModuleResolver' which acts as... well... a dummy.
engine.set_module_resolver(DummyModuleResolver::new());
```

View File

@@ -0,0 +1,26 @@
`StaticModuleResolver`
======================
{{#include ../../../links.md}}
~~~admonish abstract.small "Useful for `no-std`"
`StaticModuleResolver` is often used with [`no_std`] in embedded environments
without a file system.
~~~
Loads [modules] that are statically added.
Functions are searched in the [_global_ namespace][function namespace] by default.
```rust
use rhai::{Module, module_resolvers::StaticModuleResolver};
let module: Module = create_a_module();
let mut resolver = StaticModuleResolver::new();
resolver.insert("my_module", module);
engine.set_module_resolver(resolver);
```

View File

@@ -0,0 +1,70 @@
Compile to a Self-Contained `AST`
=================================
{{#include ../../links.md}}
```admonish tip.side "Tip"
It does not matter where the [`import`] statement occurs &mdash; e.g. deep within statements blocks
or within [function] bodies.
```
When a script [imports][`import`] external [modules] that may not be available later on, it is
possible to eagerly [_pre-resolve_][module resolver] these imports and embed them directly into a
self-contained [`AST`].
For instance, a system may periodically connect to a central source (e.g. a database) to load
scripts and compile them to [`AST`] form. Afterwards, in order to conserve bandwidth (or due to
other physical limitations), it is disconnected from the central source for self-contained
operation.
Compile a script into a _self-contained_ [`AST`] via `Engine::compile_into_self_contained`.
```rust
let mut engine = Engine::new();
// Compile script into self-contained AST using the current
// module resolver (default to `FileModuleResolver`) to pre-resolve
// 'import' statements.
let ast = engine.compile_into_self_contained(&mut scope, script)?;
// Make sure we can no longer resolve any module!
engine.set_module_resolver(DummyModuleResolver::new());
// The AST still evaluates fine, even with 'import' statements!
engine.run(&ast)?;
```
When such an [`AST`] is evaluated, [`import`] statements within are provided the _pre-resolved_
[modules] without going through the normal [module resolution][module resolver] process.
Only Static Paths
-----------------
`Engine::compile_into_self_contained` only pre-resolves [`import`] statements in the script
that are _static_, i.e. with a path that is a [string] literal.
```rust
// The following import is pre-resolved.
import "hello" as h;
if some_event() {
// The following import is pre-resolved.
import "hello" as h;
}
fn foo() {
// The following import is pre-resolved.
import "hello" as h;
}
// The following import is also pre-resolved because the expression
// is usually optimized into a single string during compilation.
import "he" + "llo" as h;
let module_name = "hello";
// The following import is NOT pre-resolved.
import module_name as h;
```

View File

@@ -0,0 +1,183 @@
Make a Module Available to Scripts
==================================
{{#include ../../links.md}}
Use Case 1 &ndash; Make It Globally Available
---------------------------------------------
`Engine::register_global_module` registers a shared [module] into the
[_global_ namespace][function namespace].
This is by far the easiest way to expose a [module]'s functionalities to Rhai.
```admonish tip.small "Tip: No qualifiers"
All [functions], [variables]/[constants] and [type iterators] can be accessed without
_namespace qualifiers_.
```
```admonish warning.small
[Sub-modules][module] are **ignored**.
```
```rust
use rhai::{Engine, Module, FuncRegistration};
let mut module = Module::new(); // new module
// Add new function.
FuncRegistration::new("inc")
.with_params_info(&["x: i64", "i64"])
.set_into_module(&mut module, |x: i64| x + 1);
// Use 'Module::set_var' to add variables.
module.set_var("MYSTIC_NUMBER", 41_i64);
// Register the module into the global namespace of the Engine.
let mut engine = Engine::new();
engine.register_global_module(module.into());
// No need to import module...
engine.eval::<i64>("inc(MYSTIC_NUMBER)")? == 42;
```
### Equivalent to `Engine::register_XXX`
```admonish question.side "Trivia"
`Engine::register_fn` etc. are actually implemented by adding functions to an
internal [module]!
```
Registering a [module] via `Engine::register_global_module` is essentially the _same_
as calling `Engine::register_fn` (or any of the `Engine::register_XXX` API) individually
on each top-level function within that [module].
```rust
// The above is essentially the same as:
let mut engine = Engine::new();
engine.register_fn("inc", |x: i64| x + 1);
engine.eval::<i64>("inc(41)")? == 42; // no need to import module
```
Use Case 2 &ndash; Make It a Static Namespace
---------------------------------------------
`Engine::register_static_module` registers a [module] and under a specific
[module namespace][function namespace].
```rust
use rhai::{Engine, Module, FuncRegistration};
let mut module = Module::new(); // new module
// Add new function.
FuncRegistration::new("inc")
.with_params_info(&["x: i64", "i64"])
.set_into_module(&mut module, |x: i64| x + 1);
// Use 'Module::set_var' to add variables.
module.set_var("MYSTIC_NUMBER", 41_i64);
// Register the module into the Engine as the static module namespace path
// 'services::calc'
let mut engine = Engine::new();
engine.register_static_module("services::calc", module.into());
// Refer to the 'services::calc' module...
engine.eval::<i64>("services::calc::inc(services::calc::MYSTIC_NUMBER)")? == 42;
```
### Expose functions to the global namespace
```admonish tip.side "Tip: Type iterators"
[Type iterators] are special &mdash; they are _always_ exposed to the
[_global_ namespace][function namespace].
```
The [`Module`] API can optionally expose functions to the [_global_ namespace][function namespace]
by setting the `namespace` parameter to `FnNamespace::Global`.
This way, [getters/setters] and [indexers] for [custom types] can work as expected.
```rust
use rhai::{Engine, Module, FuncRegistration, FnNamespace};
let mut module = Module::new(); // new module
// Add new function.
FuncRegistration::new("inc")
.with_params_info(&["x: i64", "i64"])
.with_namespace(FnNamespace::Global) // <- global namespace
.set_into_module(&mut module, |x: i64| x + 1);
// Use 'Module::set_var' to add variables.
module.set_var("MYSTIC_NUMBER", 41_i64);
// Register the module into the Engine as a static module namespace 'calc'
let mut engine = Engine::new();
engine.register_static_module("calc", module.into());
// 'inc' works when qualified by the namespace
engine.eval::<i64>("calc::inc(calc::MYSTIC_NUMBER)")? == 42;
// 'inc' also works without a namespace qualifier
// because it is exposed to the global namespace
engine.eval::<i64>("let x = calc::MYSTIC_NUMBER; x.inc()")? == 42;
engine.eval::<i64>("let x = calc::MYSTIC_NUMBER; inc(x)")? == 42;
```
Use Case 3 &ndash; Make It Dynamically Loadable
-----------------------------------------------
In order to dynamically load a custom module, there must be a [module resolver] which serves
the module when loaded via `import` statements.
The easiest way is to use, for example, the [`StaticModuleResolver`][module resolver] to hold such
a custom module.
```rust
use rhai::{Engine, Scope, Module, FuncRegistration};
use rhai::module_resolvers::StaticModuleResolver;
let mut module = Module::new(); // new module
module.set_var("answer", 41_i64); // variable 'answer' under module
FuncRegistration::new("inc")
.with_params_info(&["x: i64"])
.set_into_module(&mut module, |x: i64| x + 1);
// Create the module resolver
let mut resolver = StaticModuleResolver::new();
// Add the module into the module resolver under the name 'question'
// They module can then be accessed via: 'import "question" as q;'
resolver.insert("question", module);
// Set the module resolver into the 'Engine'
let mut engine = Engine::new();
engine.set_module_resolver(resolver);
// Use namespace-qualified variables
engine.eval::<i64>(
r#"
import "question" as q;
q::answer + 1
"#)? == 42;
// Call namespace-qualified functions
engine.eval::<i64>(
r#"
import "question" as q;
q::inc(q::answer)
"#)? == 42;
```

View File

@@ -0,0 +1,91 @@
Operator Overloading
====================
{{#include ../links.md}}
In Rhai, a lot of functionalities are actually implemented as functions, including basic operations
such as arithmetic calculations.
For example, in the expression "`a + b`", the `+` [operator] actually calls a function named "`+`"!
```rust
let x = a + b;
let x = +(a, b); // <- the above is equivalent to this function call
```
Similarly, comparison [operators] including `==`, `!=` etc. are all implemented as functions,
with the stark exception of `&&`, `||` and `??`.
~~~admonish warning.small "`&&`, `||` and `??` cannot be overloaded"
Because they [_short-circuit_]({{rootUrl}}/language/logic.md#boolean-operators), `&&`, `||` and `??` are
handled specially and _not_ via a function.
Overriding them has no effect at all.
~~~
Overload Operator via Rust Function
-----------------------------------
[Operator] functions cannot be defined in script because [operators] are usually not valid function names.
However, [operator] functions _can_ be registered via `Engine::register_fn`.
When a custom [operator] function is registered with the same name as an [operator],
it _overrides_ the built-in version. However, make sure the [_Fast Operators Mode_][fast operators]
is disabled; otherwise this will not work.
```admonish warning.small "Must turn off _Fast Operators Mode_"
The [_Fast Operators Mode_][fast operators], which is enabled by default, causes the [`Engine`]
to _ignore_ all custom-registered operator functions for [built-in operators]. This is for
performance considerations.
Disable [_Fast Operators Mode_][fast operators] via [`Engine::set_fast_operators`][options]
in order for the overloaded operators to be used.
```
```rust
use rhai::{Engine, EvalAltResult};
let mut engine = Engine::new();
fn strange_add(a: i64, b: i64) -> i64 {
(a + b) * 42
}
engine.register_fn("+", strange_add); // overload '+' operator for two integers!
engine.set_fast_operators(false); // <- IMPORTANT! must turn off Fast Operators Mode
let result: i64 = engine.eval("1 + 0"); // the overloading version is used
result == 42;
let result: f64 = engine.eval("1.0 + 0.0"); // '+' operator for two floats not overloaded
result == 1.0;
fn mixed_add(a: i64, b: bool) -> f64 { a + if b { 42 } else { 99 } }
engine.register_fn("+", mixed_add); // register '+' operator for an integer and a bool
let result: i64 = engine.eval("1 + true"); // <- normally an error...
result == 43; // ... but not now
```
```admonish danger.small "Considerations"
Use [operator] overloading for [custom types] only.
Be **very careful** when overriding built-in [operators] because users expect standard [operators] to
behave in a consistent and predictable manner, and will be annoyed if an expression involving `+`
turns into a subtraction, for example. You may think it is amusing, but users who need to get
things done won't.
[Operator] overloading also impacts [script optimization] when using [`OptimizationLevel::Full`].
See the section on [script optimization] for more details.
```

View File

@@ -0,0 +1,34 @@
Function Overloading
====================
{{#include ../links.md}}
Functions registered with the [`Engine`] can be _overloaded_ as long as the _signature_ is unique,
i.e. different functions can have the same name as long as their parameters are of different numbers
(i.e. _arity_) or different types.
New definitions _overwrite_ previous definitions of the same name, same arity and same parameter types.
~~~admonish tip "Tip: Overloading as a form of default parameter values"
Rhai does not support default values for function parameters.
However it is extremely easy to _simulate_ default parameter values via multiple overloaded
registrations of the same function name.
```rust
// The following definition of 'foo' is equivalent to the pseudo-code:
// fn foo(x = 42_i64, y = "hello", z = true) -> i64 { ... }
fn foo3(x: i64, y: &str, z: bool) -> i64 { ... }
fn foo2(x: i64, y: &str) -> i64 { foo3(x, y, true) }
fn foo1(x: i64) -> i64 { foo2(x, "hello") }
fn foo0() -> i64 { foo1(42) }
engine.register_fn("foo", foo0) // no parameters
.register_fn("foo", foo1) // 1 parameter
.register_fn("foo", foo2) // 2 parameters
.register_fn("foo", foo3); // 3 parameters
```
~~~

View File

@@ -0,0 +1,55 @@
Override a Built-in Function
============================
{{#include ../links.md}}
Any similarly-named function defined in a script _overrides_ any built-in or registered
native Rust function of the same name and number of parameters.
```js
// Override the built-in function 'to_float' when called as a method
fn to_float() {
print(`Ha! Gotcha! ${this}`);
42.0
}
let x = 123.to_float();
print(x); // what happens?
```
```admonish tip.small "Tip: Monkey patching Rhai"
Most of Rhai's built-in functionality resides in registered functions.
If you dislike any built-in function, simply provide your own implementation to
_override_ the built-in version.
The ability to modify the operating environment dynamically at runtime is called
"[monkey patching](https://en.wikipedia.org/wiki/Monkey_patch)."
It is rarely recommended, but if you need it, you need it bad.
In other words, do it only when _all else fails_. Do not monkey patch Rhai simply
because you _can_.
```
```admonish info.small "Search order for functions"
Rhai searches for the correct implementation of a function in the following order:
1. Rhai script-defined [functions],
2. Native Rust functions registered directly via the `Engine::register_XXX` API,
3. Native Rust functions in [packages] that have been loaded via `Engine::register_global_module`,
4. Native Rust or Rhai script-defined functions in [imported][`import`] [modules] that are exposed to
the global [namespace][function namespace] (e.g. via the `#[rhai_fn(global)]` attribute in a
[plugin module]),
5. Native Rust or Rhai script-defined functions in [modules] loaded via
`Engine::register_static_module` that are exposed to the global [namespace][function namespace]
(e.g. via the `#[rhai_fn(global)]` attribute in a [plugin module]),
6. [Built-in][built-in operators] functions.
```

View File

@@ -0,0 +1,44 @@
Built-In Packages
=================
{{#include ../../links.md}}
`Engine::new` creates an [`Engine`] with the `StandardPackage` loaded.
`Engine::new_raw` creates an [`Engine`] with _no_ [package] loaded.
| Package | Description | In `Core` | In `Standard` |
| ---------------------- | ----------------------------------------------------------------------------------------------------------- | :-------: | :-----------: |
| `LanguageCorePackage` | core functions for the Rhai language | yes | yes |
| `ArithmeticPackage` | arithmetic operators (e.g. `+`, `-`, `*`, `/`) for numeric types that are not built in (e.g. `u16`) | yes | yes |
| `BitFieldPackage` | basic [bit-field] functions | **no** | yes |
| `BasicIteratorPackage` | numeric ranges (e.g. `range(1, 100, 5)`), iterators for [arrays], [strings], [bit-fields] and [object maps] | yes | yes |
| `LogicPackage` | logical and comparison operators (e.g. `==`, `>`) for numeric types that are not built in (e.g. `u16`) | **no** | yes |
| `BasicStringPackage` | basic string functions (e.g. `print`, `debug`, `len`) that are not built in | yes | yes |
| `BasicTimePackage` | basic time functions (e.g. [timestamps], not available under [`no_time`] or [`no_std`]) | **no** | yes |
| `MoreStringPackage` | additional string functions, including converting common types to string | **no** | yes |
| `BasicMathPackage` | basic math functions (e.g. `sin`, `sqrt`) | **no** | yes |
| `BasicArrayPackage` | basic [array] functions (not available under [`no_index`]) | **no** | yes |
| `BasicBlobPackage` | basic [BLOB] functions (not available under [`no_index`]) | **no** | yes |
| `BasicMapPackage` | basic [object map] functions (not available under [`no_object`]) | **no** | yes |
| `BasicFnPackage` | basic methods for [function pointers] | yes | yes |
| `DebuggingPackage` | basic functions for [debugging][debugger] (requires [`debugging`]) | yes | yes |
| `CorePackage` | basic essentials | yes | yes |
| `StandardPackage` | standard library (default for `Engine::new`) | **no** | yes |
`CorePackage`
-------------
If only minimal functionalities are required, register the `CorePackage` instead.
```rust
use rhai::Engine;
use rhai::packages::{Package, CorePackage};
let mut engine = Engine::new_raw();
let package = CorePackage::new();
// Register the package into the 'Engine'.
package.register_into_engine(&mut engine);
```

View File

@@ -0,0 +1,69 @@
Create a Custom Package as an Independent Crate
===============================================
{{#include ../../links.md}}
Creating a custom [package] as an independent crate allows it to be shared by multiple projects.
```admonish abstract.small "Key concepts"
* Create a Rust crate that specifies [`rhai`](https://crates.io/crates/rhai) as dependency.
* The main `lib.rs` module can contain the [package] being constructed.
```
```admonish example.small
The projects [`rhai-rand`] and [`rhai-sci`] show simple examples of creating a custom [package]
as an independent crate.
```
Implementation
--------------
`Cargo.toml`:
```toml
[package]
name = "my-package" # 'my-package' crate
[dependencies]
rhai = "{{version}}" # assuming {{version}} is the latest version
```
`lib.rs`:
```rust
use rhai::def_package;
use rhai::plugin::*;
// This is a plugin module
#[export_module]
mod my_module {
// Constants are ignored when used as a package
pub const MY_NUMBER: i64 = 42;
pub fn greet(name: &str) -> String {
format!("hello, {}!", name)
}
pub fn get_num() -> i64 {
42
}
// This is a sub-module, but if using combine_with_exported_module!, it will
// be flattened and all functions registered at the top level.
pub mod my_sub_module {
pub fn get_sub_num() -> i64 {
0
}
}
}
// Define the package 'MyPackage' which is exported for the crate.
def_package! {
/// My own personal super package in a new crate!
pub MyPackage(module) {
combine_with_exported_module!(module, "my-functions", my_module);
}
}
```

View File

@@ -0,0 +1,256 @@
Create a Custom Package
=======================
{{#include ../../links.md}}
```admonish info.side "See also"
See also the [_One Engine Instance Per Call_]({{rootUrl}}/patterns/parallel.md) pattern.
```
The macro `def_package!` can be used to create a custom [package].
A custom [package] can aggregate many other [packages] into a single self-contained unit.
More functions can be added on top of others.
Custom [packages] are extremely useful when multiple [raw `Engine`] instances must be created such
that they all share the same set of functions.
`def_package!`
--------------
> ```rust
> def_package! {
> /// Package description doc-comment
> pub name(variable) {
> :
> // package init code block
> :
> }
>
> // Multiple packages can be defined at the same time,
> // possibly with base packages and/or code to setup an Engine.
>
> /// Package description doc-comment
> pub(crate) name(variable) : base_package_1, base_package_2, ... {
> :
> // package init code block
> :
> } |> |engine| {
> :
> // engine setup code block
> :
> }
>
> /// A private package description doc-comment
> name(variable) {
> :
> // private package init code block
> :
> }
>
> :
> }
> ```
where:
| Element | Description |
| :---------------------: | ---------------------------------------------------------------------------------------------------- |
| description | doc-comment for the [package] |
| `pub` etc. | visibility of the [package] |
| name | name of the [package], usually ending in ...`Package` |
| variable | a variable name holding a reference to the [module] forming the [package], usually `module` or `lib` |
| base_package | an external [package] type that is merged into this [package] as a dependency |
| package init code block | a code block that initializes the [package] |
| engine | a variable name holding a mutable reference to an [`Engine`] |
| engine setup code block | a code block that performs setup tasks on an [`Engine`] during registration |
Examples
--------
```rust
// Import necessary types and traits.
use rhai::def_package; // 'def_package!' macro
use rhai::packages::{ArithmeticPackage, BasicArrayPackage, BasicMapPackage, LogicPackage};
use rhai::{FuncRegistration, CustomType, TypeBuilder};
/// This is a custom type.
#[derive(Clone, CustomType)]
struct TestStruct {
foo: String,
bar: i64,
baz: bool
}
def_package! {
/// My own personal super package
// Aggregate other base packages (if any) simply by listing them after a colon.
pub MyPackage(module) : ArithmeticPackage, LogicPackage, BasicArrayPackage, BasicMapPackage
{
// Register additional Rust function.
FuncRegistration::new("get_bar_value")
.with_params_info(&["s: &mut TestStruct", "i64"])
.set_into_module(module, |s: &mut TestStruct| s.bar);
// Register a function for use as a custom operator.
FuncRegistration::new("@")
.with_namespace(FnNamespace::Global) // <- make it available globally.
.set_into_module(module, |x: i64, y: i64| x * x + y * y);
} |> |engine| {
// This optional block performs tasks on an 'Engine' instance,
// e.g. register custom types and/or custom operators/syntax.
// Register custom type.
engine.build_type::<TestStruct>();
// Define a custom operator '@' with precedence of 160
// (i.e. between +|- and *|/).
engine.register_custom_operator("@", 160).unwrap();
}
}
```
~~~admonish tip.small "Tip: Feature gates on base packages"
Base packages in the list after the colon (`:`) can also have attributes (such as feature gates)!
```rust
def_package! {
// 'BasicArrayPackage' is used only under 'arrays' feature.
pub MyPackage(module) :
ArithmeticPackage,
LogicPackage,
#[cfg(feature = "arrays")]
BasicArrayPackage
{
...
}
}
```
~~~
~~~admonish danger.small "Advanced: `Engine` setup with `|>`"
A second code block (in the syntax of a [closure]) following a right-triangle symbol (`|>`)
is run whenever the [package] is being registered.
It allows performing setup tasks directly on that [`Engine`], e.g. registering [custom types],
[custom operators] and/or [custom syntax].
```rust
def_package! {
pub MyPackage(module) {
:
:
} |> |engine| {
// Call methods on 'engine'
}
}
```
~~~
Create a Custom Package from a Plugin Module
--------------------------------------------
```admonish question.side "Trivia"
This is exactly how Rhai's built-in [packages], such as `BasicMathPackage`, are actually implemented.
```
By far the easiest way to create a custom [package] is to call `plugin::combine_with_exported_module!`
from within `def_package!` which simply merges in all the functions defined within a [plugin module].
Due to specific requirements of a [package], `plugin::combine_with_exported_module!`
_flattens_ all sub-modules (i.e. all functions and [type iterators] defined within sub-modules
are pulled up to the top level instead) and so there will not be any sub-modules added to the [package].
Variables in the [plugin module] are ignored.
```rust
// Import necessary types and traits.
use rhai::def_package;
use rhai::packages::{ArithmeticPackage, BasicArrayPackage, BasicMapPackage, LogicPackage};
use rhai::plugin::*;
// Define plugin module.
#[export_module]
mod my_plugin_module {
// Custom type.
pub type ABC = TestStruct;
// Public constant.
pub const MY_NUMBER: i64 = 42;
// Public function.
pub fn greet(name: &str) -> String {
format!("hello, {}!", name)
}
// Non-public functions are by default not exported.
fn get_private_num() -> i64 {
42
}
// Public function.
pub fn get_num() -> i64 {
get_private_num()
}
// Custom operator.
#[rhai_fn(name = "@")]
pub fn square_add(x: i64, y: i64) -> i64 {
x * x + y * y
}
// A sub-module. If using 'combine_with_exported_module!', however,
// it will be flattened and all functions registered at the top level.
//
// Because of this flattening, sub-modules are very convenient for
// putting feature gates onto large groups of functions.
#[cfg(feature = "sub-num-feature")]
pub mod my_sub_module {
// Only available under 'sub-num-feature'.
pub fn get_sub_num() -> i64 {
0
}
}
}
def_package! {
/// My own personal super package
// Aggregate other base packages (if any) simply by listing them after a colon.
pub MyPackage(module) : ArithmeticPackage, LogicPackage, BasicArrayPackage, BasicMapPackage
{
// Merge all registered functions and constants from the plugin module
// into the custom package.
//
// The sub-module 'my_sub_module' is flattened and its functions
// registered at the top level.
//
// The text string name in the second parameter can be anything
// and is reserved for future use; it is recommended to be an
// ID string that uniquely identifies the plugin module.
//
// The constant variable, 'MY_NUMBER', is ignored.
//
// This call ends up registering three functions at the top level of
// the package:
// 1) 'greet'
// 2) 'get_num'
// 3) 'get_sub_num' (flattened from 'my_sub_module')
//
combine_with_exported_module!(module, "my-mod", my_plugin_module));
} |> |engine| {
// This optional block is used to set up an 'Engine' during registration.
// Define a custom operator '@' with precedence of 160
// (i.e. between +|- and *|/).
engine.register_custom_operator("@", 160).unwrap();
}
}
```

View File

@@ -0,0 +1,60 @@
Packages
========
{{#include ../../links.md}}
The built-in library of Rhai is provided as various _packages_ that can be turned into _shared_
[modules], which in turn can be registered into the _global namespace_ of an [`Engine`] via
`Engine::register_global_module`.
Packages reside under `rhai::packages::*` and the trait `rhai::packages::Package` must be loaded in
order for packages to be used.
```admonish question.small "Trivia: Packages _are_ modules!"
Internally, a _package_ is a [module], with some conveniences to make it easier to define and use as
a standard _library_ for an [`Engine`].
Packages typically contain Rust functions that are callable within a Rhai script.
All _top-level_ functions in a package are available under the _global namespace_
(i.e. they're available without namespace qualifiers).
Sub-[modules] and [variables] are ignored in packages.
```
Share a Package Among Multiple `Engine`'s
-----------------------------------------
`Engine::register_global_module` and `Engine::register_static_module` both require _shared_ [modules].
Once a package is created (e.g. via `Package::new`), it can be registered into multiple instances of
[`Engine`], even across threads (under the [`sync`] feature).
```admonish tip.small "Tip: Sharing package"
A package only has to be created _once_ and essentially shared among multiple [`Engine`] instances.
This is particularly useful when spawning large number of [raw `Engine`'s][raw `Engine`].
```
```rust
use rhai::Engine;
use rhai::packages::Package // load the 'Package' trait to use packages
use rhai::packages::CorePackage; // the 'core' package contains basic functionalities (e.g. arithmetic)
// Create a package - can be shared among multiple 'Engine' instances
let package = CorePackage::new();
let mut engines_collection: Vec<Engine> = Vec::new();
// Create 100 'raw' Engines
for _ in 0..100 {
let mut engine = Engine::new_raw();
// Register the package into the global namespace.
package.register_into_engine(&mut engine);
engines_collection.push(engine);
}
```

View File

@@ -0,0 +1,40 @@
Printing for Custom Types
=========================
{{#include ../links.md}}
Provide These Functions
-----------------------
To use [custom types] for [`print`] and [`debug`], or convert a [custom type] into a [string],
it is necessary that the following functions, at minimum, be registered (assuming the [custom type]
is `T: Display + Debug`).
| Function | Signature | Typical implementation | Usage |
| ----------- | ------------------------------------ | ---------------------- | ---------------------------------------------------------- |
| `to_string` | <code>\|x: &mut T\| -> String</code> | `x.to_string()` | converts the [custom type] into a [string] |
| `to_debug` | <code>\|x: &mut T\| -> String</code> | `format!("{x:?}")` | converts the [custom type] into a [string] in debug format |
~~~admonish tip.small "Tip: `#[rhai_fn(global)]`"
If these functions are defined via a [plugin module], be sure to include the `#[rhai_fn(global)]` attribute
in order to make them available globally.
See [this section]({{rootUrl}}/plugins/module.md#use-rhai_fnglobal) for more details.
~~~
Also Consider These
-------------------
The following functions are implemented using `to_string` or `to_debug` by default, but can be
overloaded with custom versions.
| Function | Signature | Default | Usage |
| ------------- | ---------------------------------------------- | ----------- | ---------------------------------------------------------------------- |
| `print` | <code>\|x: &mut T\| -> String</code> | `to_string` | converts the [custom type] into a [string] for the [`print`] statement |
| `debug` | <code>\|x: &mut T\| -> String</code> | `to_debug` | converts the [custom type] into a [string] for the [`debug`] statement |
| `+` operator | <code>\|s: &str, x: T\| -> String</code> | `to_string` | concatenates the [custom type] with another [string] |
| `+` operator | <code>\|x: &mut T, s: &str\| -> String</code> | `to_string` | concatenates another [string] with the [custom type] |
| `+=` operator | <code>\|s: &mut ImmutableString, x: T\|</code> | `to_string` | appends the [custom type] to an existing [string] |

View File

@@ -0,0 +1,105 @@
Manually Register Custom Type
=============================
{{#include ../links.md}}
```admonish warning.small "Warning"
This assumes that the type is defined in an external crate and so the [`CustomType`] trait
cannot be implemented for it due to Rust's [_orphan rule_](https://doc.rust-lang.org/book/ch10-02-traits.html).
```
```admonish tip.side "Tip: Working with enums"
It is also possible to use Rust enums with Rhai.
See the pattern [Working with Enums]({{rootUrl}}/patterns/enums.md) for more details.
```
The custom type needs to be _registered_ into an [`Engine`] via:
| `Engine` API | `type_of` output |
| ------------------------------ | ------------------- |
| `register_type::<T>` | full Rust path name |
| `register_type_with_name::<T>` | friendly name |
```rust
use rhai::{Engine, EvalAltResult};
#[derive(Debug, Clone)]
struct TestStruct {
field: i64
}
impl TestStruct {
fn new() -> Self {
Self { field: 1 }
}
}
let mut engine = Engine::new();
// Register custom type with friendly name
engine.register_type_with_name::<TestStruct>("TestStruct")
.register_fn("new_ts", TestStruct::new);
// Cast result back to custom type.
let result = engine.eval::<TestStruct>(
"
new_ts() // calls 'TestStruct::new'
")?;
println!("result: {}", result.field); // prints 1
```
`type_of()` a Custom Type
-------------------------
```admonish question.side.wide "Giving types the same name?"
It is OK to register several custom types under the _same_ friendly name
and `type_of()` will faithfully return it.
How this might possibly be useful is left to the imagination of the user.
```
[`type_of()`] works fine with custom types and returns the name of the type.
If `Engine::register_type_with_name` is used to register the custom type with a special
"pretty-print" friendly name, [`type_of()`] will return that name instead.
```rust
engine.register_type::<TestStruct1>()
.register_fn("new_ts1", TestStruct1::new)
.register_type_with_name::<TestStruct2>("TestStruct")
.register_fn("new_ts2", TestStruct2::new);
let ts1_type = engine.eval::<String>("let x = new_ts1(); x.type_of()")?;
let ts2_type = engine.eval::<String>("let x = new_ts2(); x.type_of()")?;
println!("{ts1_type}"); // prints 'path::to::TestStruct'
println!("{ts2_type}"); // prints 'TestStruct'
```
`==` Operator
-------------
Many standard functions (e.g. filtering, searching and sorting) expect a custom type to be
_comparable_, meaning that the `==` operator must be registered for the custom type.
For example, in order to use the [`in`] operator with a custom type for an [array],
the `==` operator is used to check whether two values are the same.
```rust
// Assume 'TestStruct' implements `PartialEq`
engine.register_fn("==",
|item1: &mut TestStruct, item2: TestStruct| item1 == &item2
);
// Then this works in Rhai:
let item = new_ts(); // construct a new 'TestStruct'
item in array; // 'in' operator uses '=='
```

View File

@@ -0,0 +1,193 @@
Use the Low-Level API to Register a Rust Function
=================================================
{{#include ../links.md}}
When a native Rust function is registered with an [`Engine`] using the `register_XXX` API, Rhai
transparently converts all function arguments from [`Dynamic`] into the correct types before calling
the function.
For more power and flexibility, there is a _low-level_ API to work directly with [`Dynamic`] values
without the conversions.
Raw Function Registration
-------------------------
The `Engine::register_raw_fn` method is marked _volatile_, meaning that it may be changed without warning.
If this is acceptable, then using this method to register a Rust function opens up more opportunities.
```rust
engine.register_raw_fn(
"increment_by", // function name
&[ // a slice containing parameter types
std::any::TypeId::of::<i64>(), // type of first parameter
std::any::TypeId::of::<i64>() // type of second parameter
],
|context, args| { // fixed function signature
// Arguments are guaranteed to be correct in number and of the correct types.
// But remember this is Rust, so you can keep only one mutable reference at any one time!
// Therefore, get a '&mut' reference to the first argument _last_.
// Alternatively, use `args.split_first_mut()` etc. to split the slice first.
let y = *args[1].read_lock::<i64>().unwrap(); // get a reference to the second argument
// then copy it because it is a primary type
let y = args[1].take().cast::<i64>(); // alternatively, directly 'consume' it
let y = args[1].as_int().unwrap(); // alternatively, use 'as_xxx()'
let x = args[0].write_lock::<i64>().unwrap(); // get a '&mut' reference to the first argument
*x += y; // perform the action
Ok(Dynamic::UNIT) // must be 'Result<Dynamic, Box<EvalAltResult>>'
}
);
// The above is the same as (in fact, internally they are equivalent):
engine.register_fn("increment_by", |x: &mut i64, y: i64| *x += y);
```
Function Signature
------------------
The function signature passed to `Engine::register_raw_fn` takes the following form.
> ```rust
> Fn(context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result<T, Box<EvalAltResult>>
> ```
where:
| Parameter | Type | Description |
| --------- | :-------------------: | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `T` | `impl Clone` | return type of the function |
| `context` | [`NativeCallContext`] | the current _native call context_, useful for recursively calling functions on the same [`Engine`] |
| `args` | `&mut [&mut Dynamic]` | a slice containing `&mut` references to [`Dynamic`] values.<br/>The slice is guaranteed to contain enough arguments _of the correct types_. |
### Return value
The return value is the result of the function call.
Remember, in Rhai, all arguments _except_ the _first_ one are always passed by _value_ (i.e. cloned).
Therefore, it is unnecessary to ever mutate any argument except the first one, as all mutations
will be on the cloned copy.
Extract The First `&mut` Argument (If Any)
------------------------------------------
To extract the first `&mut` argument passed by reference from the `args` parameter (`&mut [&mut Dynamic]`),
use the following to get a mutable reference to the underlying value:
```rust
let value: &mut T = &mut *args[0].write_lock::<T>().unwrap();
*value = ... // overwrite the existing value of the first `&mut` parameter
```
When there is a mutable reference to the first `&mut` argument, there can be no other immutable
references to `args`, otherwise the Rust borrow checker will complain.
Therefore, always extract the mutable reference last, _after_ all other arguments are taken.
Extract Other Pass-By-Value Arguments
-------------------------------------
To extract an argument passed by value from the `args` parameter (`&mut [&mut Dynamic]`), use the following statements.
| Argument type | Access statement (`n` = argument position) | Result | Original value |
| ------------------------- | ---------------------------------------------- | :-------------------------------------: | :------------: |
| `INT` | `args[n].as_int().unwrap()` | `INT` | untouched |
| `FLOAT` | `args[n].as_float().unwrap()` | `FLOAT` | untouched |
| [`Decimal`][rust_decimal] | `args[n].as_decimal().unwrap()` | [`Decimal`][rust_decimal] | untouched |
| `bool` | `args[n].as_bool().unwrap()` | `bool` | untouched |
| `char` | `args[n].as_char().unwrap()` | `char` | untouched |
| `()` | `args[n].as_unit().unwrap()` | `()` | untouched |
| [String] | `&*args[n].as_immutable_string_ref().unwrap()` | [`&ImmutableString`][`ImmutableString`] | untouched |
| [String] (consumed) | `args[n].take().cast::<ImmutableString>()` | [`ImmutableString`] | [`()`] |
| Others | `&*args[n].read_lock::<T>().unwrap()` | `&T` | untouched |
| Others (consumed) | `args[n].take().cast::<T>()` | `T` | [`()`] |
Example &ndash; Pass a Callback to a Rust Function
--------------------------------------------------
The low-level API is useful when there is a need to interact with the scripting [`Engine`]
within a function.
The following example registers a function that takes a [function pointer] as an argument,
then calls it within the same [`Engine`]. This way, a _callback_ function can be provided
to a native Rust function.
The example also showcases the use of `FnPtr::call_raw`, a low-level API which allows binding the
`this` pointer to the function pointer call.
```rust
use rhai::{Engine, FnPtr};
let mut engine = Engine::new();
// Register a Rust function
engine.register_raw_fn(
"bar",
&[
std::any::TypeId::of::<i64>(), // parameter types
std::any::TypeId::of::<FnPtr>(),
std::any::TypeId::of::<i64>(),
],
|context, args| {
// 'args' is guaranteed to contain enough arguments of the correct types
let fp = args[1].take().cast::<FnPtr>(); // 2nd argument - function pointer
let value = args[2].take(); // 3rd argument - function argument
// The 1st argument holds the 'this' pointer.
// This should be done last as it gets a mutable reference to 'args'.
let this_ptr = args.get_mut(0).unwrap();
// Use 'FnPtr::call_raw' to call the function pointer with the context
// while also binding the 'this' pointer!
fp.call_raw(&context, Some(this_ptr), [value])
},
);
let result = engine.eval::<i64>(
r#"
fn foo(x) { this += x; } // script-defined function 'foo'
let x = 41; // object
x.bar(foo, 1); // pass 'foo' as function pointer
x
"#)?;
```
~~~admonish tip "Tip: Hold multiple references"
In order to access a value argument that is expensive to clone _while_ holding a mutable reference
to the first argument, use one of the following tactics:
1. If it is a [primary type][standard types] other than [string], use `as_xxx()` as above;
2. Directly _consume_ that argument via `arg[i].take()` as above.
3. Use `split_first_mut` to partition the slice:
```rust
// Partition the slice
let (first, rest) = args.split_first_mut().unwrap();
// Mutable reference to the first parameter, of type '&mut A'
let this_ptr = &mut *first.write_lock::<A>().unwrap();
// Immutable reference to the second value parameter, of type '&B'
// This can be mutable but there is no point because the parameter is passed by value
let value_ref = &*rest[0].read_lock::<B>().unwrap();
```
~~~

View File

@@ -0,0 +1,164 @@
Serialization and Deserialization of `Dynamic` with `serde`
===========================================================
{{#include ../links.md}}
Rhai's [`Dynamic`] type supports serialization and deserialization by
[`serde`](https://crates.io/crates/serde) via the [`serde`][features] feature.
```admonish tip.small
[`Dynamic`] works _both_ as a _serialization format_ as well as a data type that is serializable.
```
[`serde`]: https://crates.io/crates/serde
[`serde_json`]: https://crates.io/crates/serde_json
[`serde::Serialize`]: https://docs.serde.rs/serde/trait.Serialize.html
[`serde::Deserialize`]: https://docs.serde.rs/serde/trait.Deserialize.html
Serialize/Deserialize a `Dynamic`
---------------------------------
With the [`serde`][features] feature turned on, [`Dynamic`] implements [`serde::Serialize`] and
[`serde::Deserialize`], so it can easily be serialized and deserialized with [`serde`] (for example,
to and from JSON via [`serde_json`]).
```rust
let value: Dynamic = ...;
// Serialize 'Dynamic' to JSON
let json = serde_json::to_string(&value);
// Deserialize 'Dynamic' from JSON
let result: Dynamic = serde_json::from_str(&json);
```
```admonish note.small "Custom types"
[Custom types] are serialized as text strings of the value's type name.
```
```admonish warning.small "BLOB's"
[BLOB's], or byte-arrays, are serialized and deserialized as simple [arrays] for some formats such as JSON.
```
```admonish tip.small "Tip: Lighter alternative for JSON"
The [`serde_json`] crate is quite heavy.
If only _simple_ JSON parsing (i.e. only deserialization) of a hash object into a Rhai [object map] is required,
the [`Engine::parse_json`]({{rootUrl}}/language/json.md}}) method is available as a _cheap_ alternative,
but it does not provide the same level of correctness, nor are there any configurable options.
```
`Dynamic` as Serialization Format
---------------------------------
A [`Dynamic`] can be seamlessly converted to and from any type that implements [`serde::Serialize`]
and/or [`serde::Deserialize`], acting as a serialization format.
### Serialize Any Type to `Dynamic`
The function `rhai::serde::to_dynamic` automatically converts any Rust type that implements
[`serde::Serialize`] into a [`Dynamic`].
For primary types, this is usually not necessary because using [`Dynamic::from`][`Dynamic`] is much
easier and is essentially the same thing. The only difference is treatment for integer values.
`Dynamic::from` keeps different integer types intact, while `rhai::serde::to_dynamic` converts them
all into [`INT`][standard types] (i.e. the system integer type which is `i64` or `i32` depending on
the [`only_i32`] feature).
Rust `struct`'s (or any type that is marked as a `serde` map) are converted into [object maps] while
Rust `Vec`'s (or any type that is marked as a `serde` sequence) are converted into [arrays].
While it is also simple to serialize a Rust type to `JSON` via `serde`,
then use [`Engine::parse_json`]({{rootUrl}}/language/json.md) to convert it into an [object map],
`rhai::serde::to_dynamic` serializes it to [`Dynamic`] directly via `serde` without going through the `JSON` step.
```rust
use rhai::{Dynamic, Map};
use rhai::serde::to_dynamic;
#[derive(Debug, serde::Serialize)]
struct Point {
x: f64,
y: f64
}
#[derive(Debug, serde::Serialize)]
struct MyStruct {
a: i64,
b: Vec<String>,
c: bool,
d: Point
}
let x = MyStruct {
a: 42,
b: vec![ "hello".into(), "world".into() ],
c: true,
d: Point { x: 123.456, y: 999.0 }
};
// Convert the 'MyStruct' into a 'Dynamic'
let map: Dynamic = to_dynamic(x);
map.is::<Map>() == true;
```
### Deserialize a `Dynamic` into Any Type
The function `rhai::serde::from_dynamic` automatically converts a [`Dynamic`] value into any Rust type
that implements [`serde::Deserialize`].
In particular, [object maps] are converted into Rust `struct`'s (or any type that is marked as
a `serde` map) while [arrays] are converted into Rust `Vec`'s (or any type that is marked
as a `serde` sequence).
```rust
use rhai::{Engine, Dynamic};
use rhai::serde::from_dynamic;
#[derive(Debug, serde::Deserialize)]
struct Point {
x: f64,
y: f64
}
#[derive(Debug, serde::Deserialize)]
struct MyStruct {
a: i64,
b: Vec<String>,
c: bool,
d: Point
}
let engine = Engine::new();
let result: Dynamic = engine.eval(
r#"
#{
a: 42,
b: [ "hello", "world" ],
c: true,
d: #{ x: 123.456, y: 999.0 }
}
"#)?;
// Convert the 'Dynamic' object map into 'MyStruct'
let x: MyStruct = from_dynamic(&result)?;
```
```admonish warning.small "Cannot deserialize shared values"
A [`Dynamic`] containing a _shared_ value cannot be deserialized.
It will give a type error.
Use `Dynamic::flatten` to obtain a cloned copy before deserialization
(if the value is not shared, it is simply returned and not cloned).
Shared values are turned off via the [`no_closure`] feature.
```

View File

@@ -0,0 +1,17 @@
Strings Interner
================
{{#include ../links.md}}
Because [strings] are immutable (i.e. the use the type [`ImmutableString`] instead of normal Rust `String`),
each operation on a [string] actually creates a new [`ImmutableString`] instance.
A _strings interner_ can substantially reduce memory usage by reusing the same [`ImmutableString`]
instance for the same [string] content.
An [`Engine`] contains a strings interner which is enabled by default
(disabled when using a [raw `Engine`]).
The maximum number of [strings] to be interned can be set via
[`Engine::set_max_strings_interned`][options] (set to zero to disable the strings interner).

View File

@@ -0,0 +1,74 @@
`String` Parameters in Rust Functions
=====================================
{{#include ../links.md}}
~~~admonish danger "Warning: Avoid `String` parameters"
As much as possible, avoid using `String` parameters in functions.
Each `String` argument is cloned during _every_ single call to that function &ndash;
and the copy immediately thrown away right after the call.
Needless to say, it is _extremely_ inefficient to use `String` parameters.
~~~
`&str` Maps to `ImmutableString`
--------------------------------
```admonish warning.side "Common mistake"
A common mistake made by novice Rhai users is to register functions with `String` parameters.
```
Rust functions accepting parameters of `String` should use `&str` instead because it maps directly
to [`ImmutableString`] which is the type that Rhai uses to represent [strings] internally.
The parameter type `String` involves always converting an [`ImmutableString`] into a `String`
which mandates cloning it.
Using [`ImmutableString`] or `&str` is much more efficient.
```rust
fn get_len1(s: String) -> i64 { // BAD!!! Very inefficient!!!
s.len() as i64
}
fn get_len2(s: &str) -> i64 { // this is better
s.len() as i64
}
fn get_len3(s: ImmutableString) -> i64 { // the above is equivalent to this
s.len() as i64
}
engine.register_fn("len1", get_len1)
.register_fn("len2", get_len2)
.register_fn("len3", get_len3);
let len = engine.eval::<i64>("len1(x)")?; // 'x' cloned, very inefficient!!!
let len = engine.eval::<i64>("len2(x)")?; // 'x' is shared
let len = engine.eval::<i64>("len3(x)")?; // 'x' is shared
```
~~~admonish danger "`&mut String` does not work &ndash; use `&mut ImmutableString` instead"
A function with the first parameter being `&mut String` does not match a string argument passed to it,
which has type `ImmutableString`.
In fact, `&mut String` is treated as an opaque [custom type].
```rust
fn bad(s: &mut String) { ... } // '&mut String' will not match string values
fn good(s: &mut ImmutableString) { ... }
engine.register_fn("bad", bad)
.register_fn("good", good);
engine.eval(r#"bad("hello")"#)?; // <- error: function 'bad (string)' not found
engine.eval(r#"good("hello")"#)?; // <- this one works
```
~~~

View File

@@ -0,0 +1,15 @@
Traits
======
{{#include ../links.md}}
A number of traits, under the `rhai::` module namespace, provide additional functionalities.
| Trait | Description | Methods |
| ------------------------ | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------- |
| `CustomType` | trait to build a [custom type] for use with an [`Engine`] | `build` |
| `Func` | trait for creating Rust closures from script | `create_from_ast`, `create_from_script` |
| `FuncArgs` | trait for parsing function call arguments | `parse` |
| `ModuleResolver` | trait implemented by [module resolution][module resolver] services | `resolve`, `resolve_ast`, `resolve_raw` |
| `packages::Package` | trait implemented by [packages] | `init`, `init_engine`, `register_into_engine`, `register_into_engine_as`, `as_shared_module` |
| `plugin::PluginFunction` | trait implemented by [plugin] functions | `call`, `is_method_call`, `has_context`, `is_pure` |