Custom Collection Types
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.
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 |
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.
A plugin module makes defining an entire API for a custom type a snap.
Example
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.
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()
.
// 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
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 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. They are quite awkward to work with, and basically each has a full API definition that must be pre-registered.