Refine write-up on functions overloading.

This commit is contained in:
Stephen Chung 2020-05-30 22:25:01 +08:00
parent acd0f6b56b
commit 439053b153

View File

@ -704,11 +704,16 @@ let x = (42_i64).into(); // 'into()' works for standard t
let y = Dynamic::from(String::from("hello!")); // remember &str is not supported by Rhai let y = Dynamic::from(String::from("hello!")); // remember &str is not supported by Rhai
``` ```
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 types
and/or different number.
New definitions _overwrite_ previous definitions of the same name and same number/types of parameters.
Generic functions Generic functions
----------------- -----------------
Rust generic functions can be used in Rhai, but separate instances for each concrete type must be registered separately. Rust generic functions can be used in Rhai, but separate instances for each concrete type must be registered separately.
This is essentially function overloading (Rhai does not natively support generics). This essentially overloads the function with different parameter types (Rhai does not natively support generics).
```rust ```rust
use std::fmt::Display; use std::fmt::Display;
@ -729,8 +734,8 @@ fn main()
} }
``` ```
This example shows how to register multiple functions (or, in this case, multiple overloaded versions of the same function) The above example shows how to register multiple functions (or, in this case, multiple overloaded versions of the same function)
under the same name. This enables function overloading based on the number and types of parameters. under the same name.
Fallible functions Fallible functions
------------------ ------------------
@ -773,7 +778,8 @@ fn main()
Overriding built-in functions Overriding built-in functions
---------------------------- ----------------------------
Any similarly-named function defined in a script overrides any built-in function. Any similarly-named function defined in a script overrides any built-in function and any registered
native Rust function of the same name and number of parameters.
```rust ```rust
// Override the built-in function 'to_int' // Override the built-in function 'to_int'
@ -784,11 +790,13 @@ fn to_int(num) {
print(to_int(123)); // what happens? print(to_int(123)); // what happens?
``` ```
A registered function, in turn, overrides any built-in function of the same name and number/types of parameters.
Operator overloading Operator overloading
-------------------- --------------------
In Rhai, a lot of functionalities are actually implemented as functions, including basic operations such as arithmetic calculations. 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 is _not_ built-in, but calls a function named "`+`" instead! For example, in the expression "`a + b`", the `+` operator is _not_ built in, but calls a function named "`+`" instead!
```rust ```rust
let x = a + b; let x = a + b;
@ -801,7 +809,7 @@ overriding them has no effect at all.
Operator functions cannot be defined as a script function (because operators syntax are not valid function names). Operator functions cannot be defined as a script function (because operators syntax are not valid function names).
However, operator functions _can_ be registered to the [`Engine`] via the methods `Engine::register_fn`, `Engine::register_result_fn` etc. However, operator functions _can_ be registered to the [`Engine`] via the methods `Engine::register_fn`, `Engine::register_result_fn` etc.
When a custom operator function is registered with the same name as an operator, it _overloads_ (or overrides) the built-in version. When a custom operator function is registered with the same name as an operator, it overrides the built-in version.
```rust ```rust
use rhai::{Engine, EvalAltResult, RegisterFn}; use rhai::{Engine, EvalAltResult, RegisterFn};
@ -828,7 +836,7 @@ let result: i64 = engine.eval("1 + 1.0"); // prints 2.0 (normally an e
``` ```
Use operator overloading for custom types (described below) only. Use operator overloading for custom types (described below) only.
Be very careful when overloading built-in operators because script authors expect standard operators to behave in a Be very careful when overriding built-in operators because script authors expect standard operators to behave in a
consistent and predictable manner, and will be annoyed if a calculation for '`+`' turns into a subtraction, for example. consistent and predictable manner, and will be annoyed if a calculation for '`+`' turns into a subtraction, for example.
Operator overloading also impacts script optimization when using [`OptimizationLevel::Full`]. Operator overloading also impacts script optimization when using [`OptimizationLevel::Full`].
@ -915,7 +923,7 @@ engine.register_fn("update", TestStruct::update); // registers 'update(&mut Te
engine.register_fn("new_ts", TestStruct::new); // registers 'new()' engine.register_fn("new_ts", TestStruct::new); // registers 'new()'
``` ```
The custom type is then ready for us in scripts. Scripts can see the functions and methods registered earlier. The custom type is then ready for use in scripts. Scripts can see the functions and methods registered earlier.
Get the evaluation result back out from script-land just as before, this time casting to the custom type: Get the evaluation result back out from script-land just as before, this time casting to the custom type:
```rust ```rust
@ -1745,10 +1753,10 @@ The Rust type of a timestamp is `std::time::Instant`. [`type_of()`] a timestamp
The following methods (defined in the [`BasicTimePackage`](#packages) but excluded if using a [raw `Engine`]) operate on timestamps: The following methods (defined in the [`BasicTimePackage`](#packages) but excluded if using a [raw `Engine`]) operate on timestamps:
| Function | Parameter(s) | Description | | Function | Parameter(s) | Description |
| ------------------ | ---------------------------------- | -------------------------------------------------------- | | ----------------------------- | ---------------------------------- | -------------------------------------------------------- |
| `elapsed` property | _none_ | returns the number of seconds since the timestamp | | `elapsed` method and property | _none_ | returns the number of seconds since the timestamp |
| `-` operator | later timestamp, earlier timestamp | returns the number of seconds between the two timestamps | | `-` operator | later timestamp, earlier timestamp | returns the number of seconds between the two timestamps |
### Examples ### Examples
@ -1777,15 +1785,19 @@ set of types (see [built-in operators](#built-in-operators)).
"42" == 42; // false "42" == 42; // false
``` ```
Comparing two values of _different_ data types, or of unknown data types, always results in `false`. Comparing two values of _different_ data types, or of unknown data types, always results in `false`,
except for '`!=`' (not equals) which results in `true`. This is in line with intuition.
```rust ```rust
42 == 42.0; // false - i64 is different from f64 42 == 42.0; // false - i64 cannot be compared with f64
42 > "42"; // false - i64 is different from string 42 != 42.0; // true - i64 cannot be compared with f64
42 <= "42"; // false again
42 > "42"; // false - i64 cannot be compared with string
42 <= "42"; // false - i64 cannot be compared with string
let ts = new_ts(); // custom type let ts = new_ts(); // custom type
ts == 42; // false - types are not the same ts == 42; // false - types cannot be compared
ts != 42; // true - types cannot be compared
``` ```
Boolean operators Boolean operators
@ -2073,8 +2085,8 @@ This is similar to Rust and many other modern languages.
### Function overloading ### Function overloading
Functions can be _overloaded_ and are resolved purely upon the function's _name_ and the _number_ of parameters Functions defined in script can be _overloaded_ by _arity_ (i.e. they are resolved purely upon the function's _name_
(but not parameter _types_, since all parameters are the same type - [`Dynamic`]). and _number_ of parameters, but not parameter _types_ since all parameters are the same type - [`Dynamic`]).
New definitions _overwrite_ previous definitions of the same name and number of parameters. New definitions _overwrite_ previous definitions of the same name and number of parameters.
```rust ```rust
@ -2346,19 +2358,20 @@ so that it does not consume more resources that it is allowed to.
The most important resources to watch out for are: The most important resources to watch out for are:
* **Memory**: A malignant script may continuously grow an [array] or [object map] until all memory is consumed. * **Memory**: A malicous script may continuously grow an [array] or [object map] until all memory is consumed.
It may also create a large [array] or [object map] literal that exhausts all memory during parsing. It may also create a large [array] or [object map] literal that exhausts all memory during parsing.
* **CPU**: A malignant script may run an infinite tight loop that consumes all CPU cycles. * **CPU**: A malicous script may run an infinite tight loop that consumes all CPU cycles.
* **Time**: A malignant script may run indefinitely, thereby blocking the calling system which is waiting for a result. * **Time**: A malicous script may run indefinitely, thereby blocking the calling system which is waiting for a result.
* **Stack**: A malignant script may attempt an infinite recursive call that exhausts the call stack. * **Stack**: A malicous script may attempt an infinite recursive call that exhausts the call stack.
Alternatively, it may create a degenerated deep expression with so many levels that the parser exhausts the call stack Alternatively, it may create a degenerated deep expression with so many levels that the parser exhausts the call stack
when parsing the expression; or even deeply-nested statement blocks, if nested deep enough. when parsing the expression; or even deeply-nested statement blocks, if nested deep enough.
* **Overflows**: A malignant script may deliberately cause numeric over-flows and/or under-flows, divide by zero, and/or * **Overflows**: A malicous script may deliberately cause numeric over-flows and/or under-flows, divide by zero, and/or
create bad floating-point representations, in order to crash the system. create bad floating-point representations, in order to crash the system.
* **Files**: A malignant script may continuously [`import`] an external module within an infinite loop, * **Files**: A malicous script may continuously [`import`] an external module within an infinite loop,
thereby putting heavy load on the file-system (or even the network if the file is not local). thereby putting heavy load on the file-system (or even the network if the file is not local).
Furthermore, the module script may simply [`import`] itself in an infinite recursion.
Even when modules are not created from files, they still typically consume a lot of resources to load. Even when modules are not created from files, they still typically consume a lot of resources to load.
* **Data**: A malignant script may attempt to read from and/or write to data that it does not own. If this happens, * **Data**: A malicous script may attempt to read from and/or write to data that it does not own. If this happens,
it is a severe security breach and may put the entire system at risk. it is a severe security breach and may put the entire system at risk.
### Maximum number of operations ### Maximum number of operations
@ -2434,7 +2447,7 @@ Rhai by default limits function calls to a maximum depth of 128 levels (16 level
This limit may be changed via the `Engine::set_max_call_levels` method. This limit may be changed via the `Engine::set_max_call_levels` method.
When setting this limit, care must be also taken to the evaluation depth of each _statement_ When setting this limit, care must be also taken to the evaluation depth of each _statement_
within the function. It is entirely possible for a malignant script to embed an recursive call deep within the function. It is entirely possible for a malicous script to embed an recursive call deep
inside a nested expression or statement block (see [maximum statement depth](#maximum-statement-depth)). inside a nested expression or statement block (see [maximum statement depth](#maximum-statement-depth)).
The limit can be disabled via the [`unchecked`] feature for higher performance The limit can be disabled via the [`unchecked`] feature for higher performance
@ -2479,7 +2492,7 @@ engine.set_max_expr_depths(50, 5); // allow nesting up to 50 layers of
Beware that there may be multiple layers for a simple language construct, even though it may correspond Beware that there may be multiple layers for a simple language construct, even though it may correspond
to only one AST node. That is because the Rhai _parser_ internally runs a recursive chain of function calls to only one AST node. That is because the Rhai _parser_ internally runs a recursive chain of function calls
and it is important that a malignant script does not panic the parser in the first place. and it is important that a malicous script does not panic the parser in the first place.
Functions are placed under stricter limits because of the multiplicative effect of recursion. Functions are placed under stricter limits because of the multiplicative effect of recursion.
A script can effectively call itself while deep inside an expression chain within the function body, A script can effectively call itself while deep inside an expression chain within the function body,
@ -2492,7 +2505,7 @@ Make sure that `C x ( 5 + F ) + S` layered calls do not cause a stack overflow,
* `S` = maximum statement depth at global level. * `S` = maximum statement depth at global level.
A script exceeding the maximum nesting depths will terminate with a parsing error. A script exceeding the maximum nesting depths will terminate with a parsing error.
The malignant `AST` will not be able to get past parsing in the first place. The malicous `AST` will not be able to get past parsing in the first place.
This check can be disabled via the [`unchecked`] feature for higher performance This check can be disabled via the [`unchecked`] feature for higher performance
(but higher risks as well). (but higher risks as well).