Revise docs.

This commit is contained in:
Stephen Chung
2020-09-28 22:14:19 +08:00
parent 2123b0a279
commit 64c421b3d7
47 changed files with 686 additions and 377 deletions

View File

@@ -30,22 +30,22 @@ Built-in Functions
The following methods (mostly defined in the [`BasicArrayPackage`][packages] but excluded if using a [raw `Engine`]) operate on arrays:
| Function | Parameter(s) | Description |
| ------------------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `push` | element to insert | inserts an element at the end |
| `+=` operator | array, element to insert (not another array) | inserts an element at the end |
| `append` | array to append | concatenates the second array to the end of the first |
| `+=` operator | array, array to append | concatenates the second array to the end of the first |
| `+` operator | first array, second array | concatenates the first array with the second |
| `insert` | element to insert, position<br/>(beginning if <= 0, end if >= length) | inserts an element at a certain index |
| `pop` | _none_ | removes the last element and returns it ([`()`] if empty) |
| `shift` | _none_ | removes the first element and returns it ([`()`] if empty) |
| `remove` | index | removes an element at a particular index and returns it, or returns [`()`] if the index is not valid |
| `reverse` | _none_ | reverses the array |
| `len` method and property | _none_ | returns the number of elements |
| `pad` | element to pad, target length | pads the array with an element to at least a specified length |
| `clear` | _none_ | empties the array |
| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) |
| Function | Parameter(s) | Description |
| ------------------------- | -------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `push` | element to insert | inserts an element at the end |
| `append` | array to append | concatenates the second array to the end of the first |
| `+=` operator | 1) array<br/>2) element to insert (not another array) | inserts an element at the end |
| `+=` operator | 1) array<br/>2) array to append | concatenates the second array to the end of the first |
| `+` operator | 1) first array<br/>2) second array | concatenates the first array with the second |
| `insert` | 1) element to insert<br/>2) position (beginning if <= 0, end if >= length) | inserts an element at a certain index |
| `pop` | _none_ | removes the last element and returns it ([`()`] if empty) |
| `shift` | _none_ | removes the first element and returns it ([`()`] if empty) |
| `remove` | index | removes an element at a particular index and returns it, or returns [`()`] if the index is not valid |
| `reverse` | _none_ | reverses the array |
| `len` method and property | _none_ | returns the number of elements |
| `pad` | 1) target length<br/>2) element to pad | pads the array with an element to at least a specified length |
| `clear` | _none_ | empties the array |
| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) |
Use Custom Types With Arrays

View File

@@ -16,8 +16,45 @@ x = 123; // <- syntax error: cannot assign to constant
```
Unlike variables which need not have initial values (default to [`()`]),
constants must be assigned one, and it must be a constant _value_, not an expression.
constants must be assigned one, and it must be a [_literal value_](../appendix/literals.md),
not an expression.
```rust
const x = 40 + 2; // <- syntax error: cannot assign expression to constant
```
Manually Add Constant into Custom Scope
--------------------------------------
It is possible to add a constant into a custom [`Scope`] so it'll be available to scripts
running with that [`Scope`].
When added to a custom [`Scope`], a constant can hold any value, not just a literal value.
It is very useful to have a constant value hold a [custom type], which essentially acts
as a [_singleton_](../patterns/singleton.md). The singleton object can be modified via its
registered API - being a constant only prevents it from being re-assigned or operated upon by Rhai;
mutating it via a Rust function is still allowed.
```rust
use rhai::{Engine, Scope};
struct TestStruct(i64); // custom type
let engine = Engine::new()
.register_type_with_name::<TestStruct>("TestStruct") // register custom type
.register_get_set("value",
|obj: &mut TestStruct| obj.0, // property getter
|obj: &mut TestStruct, value: i64| obj.0 = value // property setter
);
let mut scope = Scope::new(); // create custom scope
scope.push_constant("MY_NUMBER", TestStruct(123_i64)); // add constant variable
engine.consume_with_scope(&mut scope, r"
MY_NUMBER.value = 42; // constant objects can be modified
print(MY_NUMBER.value); // prints 42
")?;
```

View File

@@ -3,6 +3,10 @@ Value Conversions
{{#include ../links.md}}
Convert Between Integer and Floating-Point
-----------------------------------------
The `to_float` function converts a supported number to `FLOAT` (defaults to `f64`).
The `to_int` function converts a supported number to `INT` (`i32` or `i64` depending on [`only_i32`]).
@@ -22,3 +26,31 @@ let c = 'X'; // character
print("c is '" + c + "' and its code is " + c.to_int()); // prints "c is 'X' and its code is 88"
```
Parse String into Number
------------------------
The `parse_float` function converts a [string] into a `FLOAT` (defaults to `f64`).
The `parse_int` function converts a [string] into an `INT` (`i32` or `i64` depending on [`only_i32`]).
An optional radix (2-36) can be provided to parse the [string] into a number of the specified radix.
```rust
let x = parse_float("123.4"); // parse as floating-point
x == 123.4;
type_of(x) == "f64";
let dec = parse_int("42"); // parse as decimal
let dec = parse_int("42", 10); // radix = 10 is the default
dec == 42;
type_of(dec) == "i64";
let bin = parse_int("110", 2); // parse as binary (radix = 2)
bin == 0b110;
type_of(bin) == "i64";
let hex = parse_int("ab", 16); // parse as hex (radix = 16)
hex == 0xab;
type_of(hex) == "i64";
```

View File

@@ -58,15 +58,15 @@ The `cast` method then converts the value into a specific, known type.
Alternatively, use the `try_cast` method which does not panic but returns `None` when the cast fails.
```rust
let list: Array = engine.eval("...")?; // return type is 'Array'
let item = list[0]; // an element in an 'Array' is 'Dynamic'
let list: Array = engine.eval("...")?; // return type is 'Array'
let item = list[0]; // an element in an 'Array' is 'Dynamic'
item.is::<i64>() == true; // 'is' returns whether a 'Dynamic' value is of a particular type
item.is::<i64>() == true; // 'is' returns whether a 'Dynamic' value is of a particular type
let value = item.cast::<i64>(); // if the element is 'i64', this succeeds; otherwise it panics
let value: i64 = item.cast(); // type can also be inferred
let value = item.cast::<i64>(); // 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::<i64>()?; // 'try_cast' does not panic when the cast fails, but returns 'None'
let value = item.try_cast::<i64>()?; // 'try_cast' does not panic when the cast fails, but returns 'None'
```
Type Name
@@ -76,17 +76,21 @@ The `type_name` method gets the name of the actual type as a static string slice
which can be `match`-ed against.
```rust
let list: Array = engine.eval("...")?; // return type is 'Array'
let item = list[0]; // an element in an 'Array' is 'Dynamic'
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
match item.type_name() { // 'type_name' returns the name of the actual Rust type
"i64" => ...
"alloc::string::String" => ...
"bool" => ...
"path::to::module::TestStruct" => ...
"crate::path::to::module::TestStruct" => ...
}
```
**Note:** `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.
Conversion Traits
----------------
@@ -100,4 +104,5 @@ The following conversion traits are implemented for `Dynamic`:
* `From<String>`
* `From<char>`
* `From<Vec<T>>` (into an [array])
* `From<HashMap<String, T>>` (into an [object map]).
* `From<HashMap<String, T>>` (into an [object map])
* `From<Instant>` (into a [timestamp] if not [`no_std`])

View File

@@ -10,8 +10,8 @@ This scenario is especially common when simulating object-oriented programming (
// Define object
let obj = #{
data: 42,
increment: Fn("inc_obj"), // use function pointers to
decrement: Fn("dec_obj"), // refer to method functions
increment: Fn("inc_obj"), // use function pointers to
decrement: Fn("dec_obj"), // refer to method functions
print: Fn("print_obj")
};

View File

@@ -23,43 +23,25 @@ Therefore, similar to closures in many languages, these captured shared values p
reference counting, and may be read or modified even after the variables that hold them
go out of scope and no longer exist.
Use the `is_shared` function to check whether a particular value is a shared value.
Use the `Dynamic::is_shared` function to check whether a particular value is a shared value.
Automatic currying can be turned off via the [`no_closure`] feature.
Actual Implementation
---------------------
The actual implementation de-sugars to:
1. Keeping track of what variables are accessed inside the anonymous function,
2. If a variable is not defined within the anonymous function's scope, it is looked up _outside_ the function and
in the current execution scope - where the anonymous function is created.
3. The variable is added to the parameters list of the anonymous function, at the front.
4. The variable is then converted into a **reference-counted shared value**.
An [anonymous function] which captures an external variable is the only way to create a reference-counted shared value in Rhai.
5. The shared value is then [curried][currying] into the [function pointer] itself, essentially carrying a reference to that shared value
and inserting it into future calls of the function.
This process is called _Automatic Currying_, and is the mechanism through which Rhai simulates normal closures.
Examples
--------
```rust
let x = 1; // a normal variable
x.is_shared() == false;
let f = |y| x + y; // variable 'x' is auto-curried (captured) into 'f'
x.is_shared() == true; // 'x' is now a shared value!
f.call(2) == 3; // 1 + 2 == 3
x = 40; // changing 'x'...
f.call(2) == 42; // the value of 'x' is 40 because 'x' is shared
@@ -117,6 +99,8 @@ will occur and the script will terminate with an error.
```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
@@ -152,6 +136,26 @@ x.call(f, 2);
TL;DR
-----
### Q: How is it actually implemented?
The actual implementation of closures de-sugars to:
1. Keeping track of what variables are accessed inside the anonymous function,
2. If a variable is not defined within the anonymous function's scope, it is looked up _outside_ the function and
in the current execution scope - where the anonymous function is created.
3. The variable is added to the parameters list of the anonymous function, at the front.
4. The variable is then converted into a **reference-counted shared value**.
An [anonymous function] which captures an external variable is the only way to create a reference-counted shared value in Rhai.
5. The shared value is then [curried][currying] into the [function pointer] itself, essentially carrying a reference to that shared value
and inserting it into future calls of the function.
This process is called _Automatic Currying_, and is the mechanism through which Rhai simulates normal closures.
### Q: Why are closures implemented as automatic currying?
In concept, a closure _closes_ over captured variables from the outer scope - that's why

View File

@@ -43,10 +43,12 @@ This aspect is very similar to JavaScript before ES6 modules.
// Compile a script into AST
let ast1 = engine.compile(
r#"
fn get_message() { "Hello!" } // greeting message
fn get_message() {
"Hello!" // greeting message
}
fn say_hello() {
print(get_message()); // prints message
print(get_message()); // prints message
}
say_hello();

View File

@@ -61,8 +61,8 @@ hello.call(0); // error: function not found - 'hello_world (i64)'
Global Namespace Only
--------------------
Because of their dynamic nature, function pointers cannot refer to functions in a _module_ [namespace][function namespace]
(i.e. functions in [`import`]-ed modules). They can only refer to functions within the global [namespace][function namespace].
Because of their dynamic nature, function pointers cannot refer to functions in [`import`]-ed [modules].
They can only refer to functions within the global [namespace][function namespace].
See [function namespaces] for more details.
```rust

View File

@@ -44,10 +44,13 @@ Representation of Numbers
------------------------
JSON numbers are all floating-point while Rhai supports integers (`INT`) and floating-point (`FLOAT`) if
the [`no_float`] feature is not used. Most common generators of JSON data distinguish between
integer and floating-point values by always serializing a floating-point number with a decimal point
(i.e. `123.0` instead of `123` which is assumed to be an integer). This style can be used successfully
with Rhai [object maps].
the [`no_float`] feature is not used.
Most common generators of JSON data distinguish between integer and floating-point values by always
serializing a floating-point number with a decimal point (i.e. `123.0` instead of `123` which is
assumed to be an integer).
This style can be used successfully with Rhai [object maps].
Parse JSON with Sub-Objects
@@ -68,9 +71,10 @@ A JSON object hash starting with `#{` is handled transparently by `Engine::parse
// JSON with sub-object 'b'.
let json = r#"{"a":1, "b":{"x":true, "y":false}}"#;
let new_json = json.replace("{" "#{");
// Our JSON text does not contain the '{' character, so off we go!
let new_json = json.replace("{", "#{");
// The leading '{' will also be replaced to '#{', but parse_json can handle this.
// The leading '{' will also be replaced to '#{', but 'parse_json' handles this just fine.
let map = engine.parse_json(&new_json, false)?;
map.len() == 2; // 'map' contains two properties: 'a' and 'b'

View File

@@ -47,10 +47,10 @@ an equivalent method coded in script, where the object is accessed via the `this
The following table illustrates the differences:
| Function type | Parameters | Object reference | Function signature |
| :-----------: | :--------: | :--------------------: | :-----------------------------------------------------: |
| Native Rust | _n_ + 1 | first `&mut` parameter | `fn method<T, U, V>`<br/>`(obj: &mut T, x: U, y: V) {}` |
| Rhai script | _n_ | `this` | `fn method(x, y) {}` |
| Function type | 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` (of type `&mut T`) | `Fn(x: U, y: V)` |
`&mut` is Efficient, Except for `ImmutableString`

View File

@@ -1,59 +0,0 @@
Create a Module from an AST
==========================
{{#include ../../links.md}}
It is easy to convert a pre-compiled [`AST`] into a module: just use `Module::eval_ast_as_new`.
Don't forget the [`export`] statement, otherwise there will be 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 + 1
}
fn add_len(x, y) {
x + y.len
}
// Imported modules can 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
foo,
hello,
extra as foobar; // export sub-module
"#)?;
// Convert the 'AST' into a module, using the 'Engine' to evaluate it first
//
// The second parameter ('private_namespace'), when set to true, will encapsulate
// a copy of the entire 'AST' into each function, allowing functions in the module script
// to cross-call each other. Otherwise module script functions access the global namespace.
//
// This incurs additional overhead, avoidable by setting 'private_namespace' to false.
let module = Module::eval_ast_as_new(Scope::new(), &ast, true, &engine)?;
// 'module' now can be loaded into a custom 'Scope' for future use. It contains:
// - sub-module: 'foobar' (renamed from 'extra')
// - functions: 'calc', 'add_len'
// - variables: 'abc' (renamed from 'x'), 'foo', 'hello'
```

View File

@@ -3,35 +3,16 @@ Export Variables, Functions and Sub-Modules in Module
{{#include ../../links.md}}
A _module_ can be created from a single script (or pre-compiled [`AST`]) containing global variables,
functions and sub-modules via the `Module::eval_ast_as_new` method.
When given an [`AST`], it is first evaluated, then the following items are exposed as members of the new module:
* Global variables - essentially all variables that remain in the [`Scope`] at the end of a script run - that are exported. Variables not exported (via the `export` statement) remain hidden.
* Functions not specifically marked `private`.
* Global modules that remain in the [`Scope`] at the end of a script run.
The parameter `private_namespace` in `Module::eval_ast_as_new` determines the exact behavior of
functions exposed by the module and the namespace that they can access:
| `private_namespace` value | Behavior of module functions | Namespace | Call global functions | Call functions in same module |
| :-----------------------: | ---------------------------------------------------- | :-------: | :-------------------: | :---------------------------: |
| `true` | encapsulate the entire `AST` into each function call | module | no | yes |
| `false` | register each function independently | global | yes | no |
Global Variables
----------------
Export Global Variables
----------------------
The `export` statement, which can only be at global level, exposes selected variables as members of a module.
Variables not exported are _private_ and hidden. They are merely used to initialize the module,
but cannot be accessed from outside.
Everything exported from a module is **constant** (**read-only**).
Everything exported from a module is **constant** (i.e. read-only).
```rust
// This is a module script.
@@ -53,8 +34,8 @@ export x as answer; // the variable 'x' is exported under the alias 'answer'
```
Functions
---------
Export Functions
----------------
All functions are automatically exported, _unless_ it is explicitly opt-out with the [`private`] prefix.

View File

@@ -10,25 +10,23 @@ If an [object map]'s property holds a [function pointer], the property can simpl
a normal method in method-call syntax. This is a _short-hand_ to avoid the more verbose syntax
of using the `call` function keyword.
When a property holding a [function pointer] (which incudes [closures]) is called like a method,
When a property holding a [function pointer] or a [closure] is called like a method,
what happens next depends on whether the target function is a native Rust function or
a script-defined function.
If it is a registered native Rust method function, it is called directly.
* If it is a registered native Rust function, it is called directly in _method-call_ style with the [object map] inserted as the first argument.
If it is a script-defined function, the `this` variable within the function body is bound
to the [object map] before the function is called. There is no way to simulate this behavior
via a normal function-call syntax because all scripted function arguments are passed by value.
* If it is a script-defined function, the `this` variable within the function body is bound to the [object map] before the function is called.
```rust
let obj = #{
data: 40,
action: || this.data += x // 'action' holds a function pointer which is a closure
action: || this.data += x // 'action' holds a closure
};
obj.action(2); // Calls the function pointer with `this` bound to 'obj'
obj.action(2); // calls the function pointer with `this` bound to 'obj'
obj.call(obj.action, 2); // The above de-sugars to this
obj.call(obj.action, 2); // <- the above de-sugars to this
obj.data == 42;

View File

@@ -57,17 +57,17 @@ Built-in Functions
The following methods (defined in the [`BasicMapPackage`][packages] but excluded if using a [raw `Engine`])
operate on object maps:
| Function | Parameter(s) | Description |
| ---------------------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `has` | property name | does the object map contain a property of a particular name? |
| `len` | _none_ | returns the number of properties |
| `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 | first object map, second object map | merges the first object map with the second |
| `fill_with` | second object map | adds in all properties of the second object map that do not exist in the object map |
| `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`] |
| Function | Parameter(s) | Description |
| ---------------------- | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `has` | property name | does the object map contain a property of a particular name? |
| `len` | _none_ | returns the number of properties |
| `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 | 1) first object map<br/>2) second object map | merges the first object map with the second |
| `fill_with` | second object map | adds in all properties of the second object map that do not exist in the object map |
| `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`] |
Examples

View File

@@ -6,20 +6,20 @@ Built-in String Functions
The following standard methods (mostly defined in the [`MoreStringPackage`][packages] but excluded if
using a [raw `Engine`]) operate on [strings]:
| Function | Parameter(s) | Description |
| ------------------------- | ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- |
| `len` method and property | _none_ | returns the number of characters (not number of bytes) in the string |
| `pad` | character to pad, target length | pads the string with an character to at least a specified length |
| `+=` operator, `append` | character/string to append | Adds a character or a string to the end of another string |
| `clear` | _none_ | empties the string |
| `truncate` | target length | cuts off the string at exactly a specified number of characters |
| `contains` | character/sub-string to search for | checks if a certain character or sub-string occurs in the string |
| `index_of` | character/sub-string to search for, start index _(optional)_ | returns the index that a certain character or sub-string occurs in the string, or -1 if not found |
| `sub_string` | start index, length _(optional)_ | extracts a sub-string (to the end of the string if length is not specified) |
| `split` | delimiter character/string | splits the string by the specified delimiter, returning an [array] of string segments; not available under [`no_index`] |
| `crop` | start index, length _(optional)_ | retains only a portion of the string (to the end of the string if length is not specified) |
| `replace` | target character/sub-string, replacement character/string | replaces a sub-string with another |
| `trim` | _none_ | trims the string of whitespace at the beginning and end |
| Function | Parameter(s) | Description |
| ------------------------- | --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `len` method and property | _none_ | returns the number of characters (not number of bytes) in the string |
| `pad` | 1) character to pad<br/>2) target length | pads the string with an character to at least a specified length |
| `+=` operator, `append` | character/string to append | Adds a character or a string to the end of another string |
| `clear` | _none_ | empties the string |
| `truncate` | target length | cuts off the string at exactly a specified number of characters |
| `contains` | character/sub-string to search for | checks if a certain character or sub-string occurs in the string |
| `index_of` | 1) character/sub-string to search for<br/>2) start index _(optional)_ | returns the index that a certain character or sub-string occurs in the string, or -1 if not found |
| `sub_string` | 1) start index<br/>2) length _(optional)_ | extracts a sub-string (to the end of the string if length is not specified) |
| `split` | delimiter character/string | splits the string by the specified delimiter, returning an [array] of string segments; not available under [`no_index`] |
| `crop` | 1) start index<br/>2) length _(optional)_ | retains only a portion of the string (to the end of the string if length is not specified) |
| `replace` | 1) target character/sub-string<br/>2) replacement character/string | replaces a sub-string with another |
| `trim` | _none_ | trims the string of whitespace at the beginning and end |
Examples
--------

View File

@@ -43,17 +43,17 @@ Hex sequences map to ASCII characters, while '`\u`' maps to 16-bit common Unicod
Standard escape sequences:
| Escape sequence | Meaning |
| --------------- | ------------------------------ |
| `\\` | back-slash `\` |
| `\t` | tab |
| `\r` | carriage-return `CR` |
| `\n` | line-feed `LF` |
| `\"` | double-quote `"` in strings |
| `\'` | single-quote `'` in characters |
| `\x`_xx_ | Unicode in 2-digit hex |
| `\u`_xxxx_ | Unicode in 4-digit hex |
| `\U`_xxxxxxxx_ | Unicode in 8-digit hex |
| Escape sequence | Meaning |
| --------------- | -------------------------------- |
| `\\` | back-slash `\` |
| `\t` | tab |
| `\r` | carriage-return `CR` |
| `\n` | line-feed `LF` |
| `\"` | double-quote `"` |
| `\'` | single-quote `'` |
| `\x`_xx_ | ASCII character in 2-digit hex |
| `\u`_xxxx_ | Unicode character in 4-digit hex |
| `\U`_xxxxxxxx_ | Unicode character in 8-digit hex |
Differences from Rust Strings

View File

@@ -24,3 +24,24 @@ if type_of(x) == "string" {
do_something_with_string(x);
}
```
Custom Types
------------
`type_of()` a [custom type] returns:
* if registered via `Engine::register_type_with_name` - the registered name
* if registered via `Engine::register_type` - the full Rust path name
```rust
struct TestStruct1;
struct TestStruct2;
engine
// type_of(struct1) == "crate::path::to::module::TestStruct1"
.register_type::<TestStruct1>()
// type_of(struct2) == "MyStruct"
.register_type_with_name::<TestStruct2>("MyStruct");
```