15 KiB
Object Maps
{{#include ../links.md}}
Always limit the [maximum size of object maps].
Object maps are hash dictionaries. Properties are all [Dynamic
] and can be freely added and retrieved.
The Rust type of a Rhai object map is rhai::Map
.
Currently it is an alias to BTreeMap<SmartString, Dynamic>
.
[type_of()
] an object map returns "map"
.
Object maps are disabled via the [no_object
] feature.
Normally, when [properties][getters/setters] are accessed, copies of the data values are made.
This is normally slow.
Object maps have special treatment – properties are accessed via _references_, meaning that
no copies of data values are made.
This makes object map access fast, especially when deep within a properties chain.
```rust
// 'obj' is a normal custom type
let x = obj.a.b.c.d;
// The above is equivalent to:
let a_value = obj.a; // temp copy of 'a'
let b_value = a_value.b; // temp copy of 'b'
let c_value = b_value.c; // temp copy of 'c'
let d_value = c_value.d; // temp copy of 'd'
let x = d_value;
// 'map' is an object map
let x = map.a.b.c.d; // direct access to 'd'
// 'a', 'b' and 'c' are not copied
map.a.b.c.d = 42; // directly modifies 'd' in 'a', 'b' and 'c'
// no copy of any property value is made
map.a.b.c.d.calc(); // directly calls 'calc' on 'd'
// no copy of any property value is made
```
[`SmartString`] is used because most object map properties are short (at least shorter than 23 characters)
and ASCII-based, so they can usually be stored inline without incurring the cost of an allocation.
The vast majority of object maps contain just a few properties.
`BTreeMap` performs significantly better than `HashMap` when the number of entries is small.
Literal Syntax
Object map literals are built within braces #{
... }
with name:
value pairs separated by
commas ,
:
#{
property:
value,
...,
property:
value}
#{
property:
value,
...,
property:
value,
}
// trailing comma is OK
The property name can be a simple identifier following the same naming rules as [variables], or a [string literal][literals] without interpolation.
Property Access Syntax
Dot notation
The dot notation allows only property names that follow the same naming rules as [variables].
object
.
property
Elvis notation
The [Elvis notation][elvis] is similar to the dot notation except that it returns [()
] if the object
itself is [()
].
// returns () if object is ()
object?.
property
// no action if object is ()
object?.
property=
value;
Index notation
The index notation allows setting/getting properties of arbitrary names (even the empty [string]).
object
[
property]
Handle Non-Existent Properties
Trying to read a non-existent property returns [()
] instead of causing an error.
This is similar to JavaScript where accessing a non-existent property returns undefined
.
let map = #{ foo: 42 };
// Regular property access
let x = map.foo; // x == 42
// Non-existent property
let x = map.bar; // x == ()
It is possible to force Rhai to return an `EvalAltResult:: ErrorPropertyNotFound` via
[`Engine:: set_fail_on_invalid_map_property`][options].
For fine-tuned control on what happens when a non-existent property is accessed,
see [_Non-Existent Property Handling for Object Maps_](object-maps-missing-prop.md).
Check for property existence
Use the [in
] operator to check whether a property exists in an object-map.
let map = #{ foo: 42 };
"foo" in map == true;
"bar" in map == false;
Short-circuit non-existent property access
Use the [Elvis operator][elvis] (?.
) to short-circuit further processing if the object is [()
].
x.a.b.foo(); // <- error if 'x', 'x.a' or 'x.a.b' is ()
x.a.b = 42; // <- error if 'x' or 'x.a' is ()
x?.a?.b?.foo(); // <- ok! returns () if 'x', 'x.a' or 'x.a.b' is ()
x?.a?.b = 42; // <- ok even if 'x' or 'x.a' is ()
Default property value
Using the null-coalescing operator to give non-existent properties default values.
let map = #{ foo: 42 };
// Regular property access
let x = map.foo; // x == 42
// Non-existent property
let x = map.bar; // x == ()
// Default value for property
let x = map.bar ?? 42; // x == 42
Built-in Functions
The following methods (defined in the [BasicMapPackage
][built-in packages] but excluded when using
a [raw Engine
]) operate on object maps.
Function | Parameter(s) | Description |
---|---|---|
get |
property name | gets a copy of the value of a certain property ([() ] if the property does not exist); behavior is not affected by [Engine::fail_on_invalid_map_property ][options] |
set |
|
sets a certain property to a new value (property is added if not already exists) |
len |
none | returns the number of properties |
is_empty |
none | returns true if the object map is empty |
clear |
none | empties the object map |
remove |
property name | removes a certain property and returns it ([() ] if the property does not exist) |
+= operator, mixin |
second object map | mixes in all the properties of the second object map to the first (values of properties with the same names replace the existing values) |
+ operator |
|
merges the first object map with the second |
== operator |
|
are the two object maps the same (elements compared with the == operator, if defined)? |
!= operator |
|
are the two object maps different (elements compared with the == operator, if defined)? |
fill_with |
second object map | adds in all properties of the second object map that do not exist in the object map |
contains , [in ] operator |
property name | does the object map contain a property of a particular name? |
keys |
none | returns an [array] of all the property names (in random order), not available under [no_index ] |
values |
none | returns an [array] of all the property values (in random order), not available under [no_index ] |
drain |
[function pointer] to predicate (usually a [closure]) | removes all elements (returning them) that return true when called with the predicate function taking the following parameters:
|
retain |
[function pointer] to predicate (usually a [closure]) | removes all elements (returning them) that do not return true when called with the predicate function taking the following parameters:
|
filter |
[function pointer] to predicate (usually a [closure]) | constructs a object map with all elements that return true when called with the predicate function taking the following parameters:
|
to_json |
none | returns a JSON representation of the object map ([() ] is mapped to null , all other data types must be supported by JSON) |
Examples
let y = #{ // object map literal with 3 properties
a: 1,
bar: "hello",
"baz!$@": 123.456, // like JavaScript, you can use any string as property names...
"": false, // even the empty string!
`hello`: 999, // literal strings are also OK
a: 42, // <- syntax error: duplicated property name
`a${2}`: 42, // <- syntax error: property name cannot have string interpolation
};
y.a = 42; // access via dot notation
y.a == 42;
y.baz!$@ = 42; // <- syntax error: only proper variable names allowed in dot notation
y."baz!$@" = 42; // <- syntax error: strings not allowed in dot notation
y["baz!$@"] = 42; // access via index notation is OK
"baz!$@" in y == true; // use 'in' to test if a property exists in the object map
("z" in y) == false;
ts.obj = y; // object maps can be assigned completely (by value copy)
let foo = ts.list.a;
foo == 42;
let foo = #{ a:1, }; // trailing comma is OK
let foo = #{ a:1, b:2, c:3 }["a"];
let foo = #{ a:1, b:2, c:3 }.a;
foo == 1;
fn abc() {
#{ a:1, b:2, c:3 } // a function returning an object map
}
let foo = abc().b;
foo == 2;
let foo = y["a"];
foo == 42;
y.contains("a") == true;
y.contains("xyz") == false;
y.xyz == (); // a non-existent property returns '()'
y["xyz"] == ();
y.len == (); // an object map has no property getter function
y.len() == 3; // method calls are OK
y.remove("a") == 1; // remove property
y.len() == 2;
y.contains("a") == false;
for name in y.keys() { // get an array of all the property names via 'keys'
print(name);
}
for val in y.values() { // get an array of all the property values via 'values'
print(val);
}
y.clear(); // empty the object map
y.len() == 0;
No Support for Property Getters
In order not to affect the speed of accessing properties in an object map, new [property getters][getters/setters] cannot be registered because they conflict with the syntax of property access.
A [property getter][getters/setters] function registered via Engine::register_get
, for example,
for a Map
will never be found – instead, the property will be looked up in the object map.
Properties should be registered as methods instead:
map.len // access property 'len', returns '()' if not found
map.len() // 'len' method - returns the number of properties
map.keys // access property 'keys', returns '()' if not found
map.keys() // 'keys' method - returns array of all property names
map.values // access property 'values', returns '()' if not found
map.values() // 'values' method - returns array of all property values