Interop `Dynamic` Data with Rust
================================
{{#include ../links.md}}
Create a `Dynamic` from Rust Type
---------------------------------
| Rust type
`T: Clone`,
`K: Into` | Unavailable under | Use API |
| ------------------------------------------------------------------ | :-----------------------: | ---------------------------- |
| `INT` (`i64` or `i32`) | | `value.into()` |
| `FLOAT` (`f64` or `f32`) | [`no_float`] | `value.into()` |
| [`Decimal`][rust_decimal] (requires [`decimal`]) | | `value.into()` |
| `bool` | | `value.into()` |
| [`()`] | | `value.into()` |
| [`String`][string], [`&str`][string], [`ImmutableString`] | | `value.into()` |
| `char` | | `value.into()` |
| [`Array`][array] | [`no_index`] | `Dynamic::from_array(value)` |
| [`Blob`][BLOB] | [`no_index`] | `Dynamic::from_blob(value)` |
| `Vec`, `&[T]`, `Iterator` | [`no_index`] | `value.into()` |
| [`Map`][object map] | [`no_object`] | `Dynamic::from_map(value)` |
| `HashMap`, `HashSet`,
`BTreeMap`, `BTreeSet` | [`no_object`] | `value.into()` |
| [`INT..INT`][range], [`INT..=INT`][range] | | `value.into()` |
| `Rc>` or `Arc>` | [`no_closure`] | `value.into()` |
| [`Instant`][timestamp] | [`no_time`] or [`no_std`] | `value.into()` |
| All types (including above) | | `Dynamic::from(value)` |
Type Checking and Casting
-------------------------
~~~admonish tip.side "Tip: `try_cast` and `try_cast_result`"
The `try_cast` method does not panic but returns `None` upon failure.
The `try_cast_result` method also does not panic but returns the original value upon failure.
~~~
A [`Dynamic`] value's actual type can be checked via `Dynamic::is`.
The `cast` method then converts the value into a specific, known type.
Use `clone_cast` to clone a reference to [`Dynamic`].
```rust
let list: Array = engine.eval("...")?; // return type is 'Array'
let item = list[0].clone(); // an element in an 'Array' is 'Dynamic'
item.is::() == true; // 'is' returns whether a 'Dynamic' value is of a particular type
let value = item.cast::(); // if the element is 'i64', this succeeds; otherwise it panics
let value: i64 = item.cast(); // type can also be inferred
let value = item.try_cast::()?; // 'try_cast' does not panic when the cast fails, but returns 'None'
let value = list[0].clone_cast::(); // use 'clone_cast' on '&Dynamic'
let value: i64 = list[0].clone_cast();
```
Type Name and Matching Types
----------------------------
The `type_name` method gets the name of the actual type as a static string slice,
which can be `match`-ed against.
This is a very simple and direct way to act on a [`Dynamic`] value based on the actual type of
the data value.
```rust
let list: Array = engine.eval("...")?; // return type is 'Array'
let item = list[0]; // an element in an 'Array' is 'Dynamic'
match item.type_name() { // 'type_name' returns the name of the actual Rust type
"()" => ...
"i64" => ...
"f64" => ...
"rust_decimal::Decimal" => ...
"core::ops::range::Range" => ...
"core::ops::range::RangeInclusive" => ...
"alloc::string::String" => ...
"bool" => ...
"char" => ...
"rhai::FnPtr" => ...
"std::time::Instant" => ...
"crate::path::to::module::TestStruct" => ...
:
}
```
```admonish warning.small "Always full path name"
`type_name` always returns the _full_ Rust path name of the type, even when the type
has been registered with a friendly name via `Engine::register_type_with_name`.
This behavior is different from that of the [`type_of`][`type_of()`] function in Rhai.
```
Getting a Reference to Data
---------------------------
Use `Dynamic::read_lock` and `Dynamic::write_lock` to get an immutable/mutable reference to the data
inside a [`Dynamic`].
```rust
struct TheGreatQuestion {
answer: i64
}
let question = TheGreatQuestion { answer: 42 };
let mut value: Dynamic = Dynamic::from(question);
let q_ref: &TheGreatQuestion =
&*value.read_lock::().unwrap();
// ^^^^^^^^^^^^^^^^^^^^ cast to data type
println!("answer = {}", q_ref.answer); // prints 42
let q_mut: &mut TheGreatQuestion =
&mut *value.write_lock::().unwrap();
// ^^^^^^^^^^^^^^^^^^^^ cast to data type
q_mut.answer = 0; // mutate value
let value = value.cast::();
println!("new answer = {}", value.answer); // prints 0
```
~~~admonish question "TL;DR – Why `read_lock` and `write_lock`?"
As the naming shows, something is _locked_ in order to allow accessing the data within a [`Dynamic`],
and that something is a _shared value_ created by capturing variables from [closures].
Shared values are implemented as `Rc>` (`Arc>` under [`sync`]).
If the value is _not_ a shared value, or if running under [`no_closure`] where there is
no capturing, this API de-sugars to a simple reference cast.
In other words, there is no locking and reference counting overhead for the vast majority of
non-shared values.
If the value _is_ a shared value, then it is first _locked_ and the returned _lock guard_
allows access to the underlying value in the specified type.
~~~
Methods and Traits
------------------
The following methods are available when working with [`Dynamic`]:
| Method | Not available under | Return type | Description |
| --------------- | :-------------------------: | :-----------------------: | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type_name` | | `&str` | name of the value's type |
| `into_shared` | [`no_closure`] | [`Dynamic`] | turn the value into a _shared_ value |
| `flatten_clone` | | [`Dynamic`] | clone the value (a _shared_ value, if any, is cloned into a separate copy) |
| `flatten` | | [`Dynamic`] | clone the value into a separate copy if it is _shared_ and there are multiple outstanding references, otherwise _shared_ values are turned unshared |
| `read_lock` | [`no_closure`] (pass thru') | `Option<` _guard to_ `T>` | lock the value for reading |
| `write_lock` | [`no_closure`] (pass thru') | `Option<` _guard to_ `T>` | lock the value exclusively for writing |
| `deep_scan` | | | recursively scan for [`Dynamic`] values (e.g. items inside an [array] or [object map], or [curried arguments][currying] in a [function pointer]) |
### Constructor instance methods
| Method | Not available under | Value type | Data type |
| ---------------- | :-----------------------: | :---------------------------------------------------------: | :-----------------------: |
| `from_bool` | | `bool` | `bool` |
| `from_int` | | `INT` | integer number |
| `from_float` | [`no_float`] | `FLOAT` | floating-point number |
| `from_decimal` | non-[`decimal`] | [`Decimal`][rust_decimal] | [`Decimal`][rust_decimal] |
| `from_str` | | `&str` | [string] |
| `from_char` | | `char` | [character] |
| `from_array` | [`no_index`] | `Vec` | [array] |
| `from_blob` | [`no_index`] | `Vec` | [BLOB] |
| `from_map` | [`no_object`] | `Map` | [object map] |
| `from_timestamp` | [`no_time`] or [`no_std`] | `std::time::Instant` ([`instant::Instant`] if [WASM] build) | [timestamp] |
| `from` | | `T` | [custom type] |
### Detection methods
| Method | Not available under | Return type | Description |
| -------------- | :-----------------------: | :---------: | ---------------------------------------------------------------------- |
| `is` | | `bool` | is the value of type `T`? |
| `is_variant` | | `bool` | is the value a trait object (i.e. not one of Rhai's [standard types])? |
| `is_read_only` | | `bool` | is the value [constant]? A [constant] value should not be modified. |
| `is_shared` | [`no_closure`] | `bool` | is the value _shared_ via a [closure]? |
| `is_locked` | [`no_closure`] | `bool` | is the value _shared_ and locked (i.e. currently being read)? |
| `is_unit` | | `bool` | is the value [`()`]? |
| `is_int` | | `bool` | is the value an integer? |
| `is_float` | [`no_float`] | `bool` | is the value a floating-point number? |
| `is_decimal` | non-[`decimal`] | `bool` | is the value a [`Decimal`][rust_decimal]? |
| `is_bool` | | `bool` | is the value a `bool`? |
| `is_char` | | `bool` | is the value a [character]? |
| `is_string` | | `bool` | is the value a [string]? |
| `is_array` | [`no_index`] | `bool` | is the value an [array]? |
| `is_blob` | [`no_index`] | `bool` | is the value a [BLOB]? |
| `is_map` | [`no_object`] | `bool` | is the value an [object map]? |
| `is_timestamp` | [`no_time`] or [`no_std`] | `bool` | is the value a [timestamp]? |
### Casting methods
The following methods cast a [`Dynamic`] into a specific type:
| Method | Not available under | Return type (error is name of actual type if `&str`) |
| ------------------------- | :-----------------: | :------------------------------------------------------------------------: |
| `cast` | | `T` (panics on failure) |
| `try_cast` | | `Option` |
| `try_cast_result` | | `Result` |
| `clone_cast` | | cloned copy of `T` (panics on failure) |
| `as_unit` | | `Result<(), &str>` |
| `as_int` | | `Result` |
| `as_float` | [`no_float`] | `Result` |
| `as_decimal` | non-[`decimal`] | [`Result`][rust_decimal] |
| `as_bool` | | `Result` |
| `as_char` | | `Result` |
| `as_immutable_string_ref` | | [`Result, &str>`][`ImmutableString`] |
| `as_immutable_string_mut` | | [`Result, &str>`][`ImmutableString`] |
| `as_array_ref` | [`no_index`] | [`Result, &str>`][array] |
| `as_array_mut` | [`no_index`] | [`Result, &str>`][array] |
| `as_blob_ref` | [`no_index`] | [`Result, &str>`][BLOB] |
| `as_blob_mut` | [`no_index`] | [`Result, &str>`][BLOB] |
| `as_map_ref` | [`no_object`] | [`Result, &str>`][object map] |
| `as_map_mut` | [`no_object`] | [`Result, &str>`][object map] |
| `into_string` | | `Result` |
| `into_immutable_string` | | [`Result`][`ImmutableString`] |
| `into_array` | [`no_index`] | [`Result`][array] |
| `into_blob` | [`no_index`] | [`Result`][BLOB] |
| `into_typed_array` | [`no_index`] | `Result, &str>` |
### Constructor traits
The following constructor traits are implemented for [`Dynamic`] where `T: Clone`:
| Trait | Not available under | Data type |
| ------------------------------------------------------------------------------ | :----------------------------: | :-----------------------: |
| `From<()>` | | `()` |
| `From` | | integer number |
| `From` | [`no_float`] | floating-point number |
| `From` | non-[`decimal`] | [`Decimal`][rust_decimal] |
| `From` | | `bool` |
| `From>`
e.g. `From`, `From<&str>` | | [`ImmutableString`] |
| `From` | | [character] |
| `From>` | [`no_index`] | [array] |
| `From<&[T]>` | [`no_index`] | [array] |
| `From, T>>`
e.g. `From>` | [`no_object`] | [object map] |
| `From>>`
e.g. `From>` | [`no_object`] | [object map] |
| `From, T>>`
e.g. `From>` | [`no_object`] or [`no_std`] | [object map] |
| `From>>`
e.g. `From>` | [`no_object`] or [`no_std`] | [object map] |
| `From` | | [function pointer] |
| `From` | [`no_time`] or [`no_std`] | [timestamp] |
| `From>>` | [`sync`] or [`no_closure`] | [`Dynamic`] |
| `From>>` ([`sync`]) | non-[`sync`] or [`no_closure`] | [`Dynamic`] |
| `FromIterator>` | [`no_index`] | [array] |