Add doc tests to Scope.
This commit is contained in:
parent
c4498d147d
commit
2bb195cd65
170
README.md
170
README.md
@ -8,18 +8,21 @@ Rhai - Embedded Scripting for Rust
|
|||||||
![crates.io](https://img.shields.io/crates/d/rhai)
|
![crates.io](https://img.shields.io/crates/d/rhai)
|
||||||
[![API Docs](https://docs.rs/rhai/badge.svg)](https://docs.rs/rhai/)
|
[![API Docs](https://docs.rs/rhai/badge.svg)](https://docs.rs/rhai/)
|
||||||
|
|
||||||
Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way to add scripting to any application.
|
Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way
|
||||||
|
to add scripting to any application.
|
||||||
|
|
||||||
Rhai's current features set:
|
Rhai's current features set:
|
||||||
|
|
||||||
* `no-std` support
|
* `no-std` support
|
||||||
* Easy integration with Rust functions and data types, supporting getter/setter methods
|
* Easy integration with Rust native functions and data types, including getter/setter methods
|
||||||
* Easily call a script-defined function from Rust
|
* Easily call a script-defined function from Rust
|
||||||
|
* Freely pass variables/constants into a script via an external [`Scope`]
|
||||||
* Fairly efficient (1 million iterations in 0.75 sec on my 5 year old laptop)
|
* Fairly efficient (1 million iterations in 0.75 sec on my 5 year old laptop)
|
||||||
* Low compile-time overhead (~0.6 sec debug/~3 sec release for script runner app)
|
* Low compile-time overhead (~0.6 sec debug/~3 sec release for script runner app)
|
||||||
* Easy-to-use language similar to JS+Rust
|
* Easy-to-use language similar to JS+Rust
|
||||||
* Support for overloaded functions
|
* Support for overloaded functions
|
||||||
* Compiled script is optimized for repeat evaluations
|
* Compiled script is optimized for repeat evaluations
|
||||||
|
* Support for minimal builds by excluding unneeded language features
|
||||||
* Very few additional dependencies (right now only [`num-traits`](https://crates.io/crates/num-traits/)
|
* Very few additional dependencies (right now only [`num-traits`](https://crates.io/crates/num-traits/)
|
||||||
to do checked arithmetic operations); for [`no_std`] builds, a number of additional dependencies are
|
to do checked arithmetic operations); for [`no_std`] builds, a number of additional dependencies are
|
||||||
pulled in to provide for functionalities that used to be in `std`.
|
pulled in to provide for functionalities that used to be in `std`.
|
||||||
@ -70,7 +73,8 @@ Optional features
|
|||||||
| `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
|
| `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
|
||||||
| `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, [`Engine`], [`Scope`] and `AST` are all `Send + Sync`. |
|
| `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, [`Engine`], [`Scope`] and `AST` are all `Send + Sync`. |
|
||||||
|
|
||||||
By default, Rhai includes all the standard functionalities in a small, tight package. Most features are here to opt-**out** of certain functionalities that are not needed.
|
By default, Rhai includes all the standard functionalities in a small, tight package.
|
||||||
|
Most features are here to opt-**out** of certain functionalities that are not needed.
|
||||||
Excluding unneeded functionalities can result in smaller, faster builds as well as less bugs due to a more restricted language.
|
Excluding unneeded functionalities can result in smaller, faster builds as well as less bugs due to a more restricted language.
|
||||||
|
|
||||||
[`unchecked`]: #optional-features
|
[`unchecked`]: #optional-features
|
||||||
@ -209,8 +213,8 @@ Compiling a script file is also supported:
|
|||||||
let ast = engine.compile_file("hello_world.rhai".into())?;
|
let ast = engine.compile_file("hello_world.rhai".into())?;
|
||||||
```
|
```
|
||||||
|
|
||||||
Rhai also allows working _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust - via `call_fn`
|
Rhai also allows working _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust -
|
||||||
or its cousins `call_fn1` (one argument) and `call_fn0` (no argument).
|
via `call_fn` or its cousins `call_fn1` (one argument) and `call_fn0` (no argument).
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Define functions in a script.
|
// Define functions in a script.
|
||||||
@ -295,12 +299,14 @@ The following primitive types are supported natively:
|
|||||||
|
|
||||||
[`()`]: #values-and-types
|
[`()`]: #values-and-types
|
||||||
|
|
||||||
All types are treated strictly separate by Rhai, meaning that `i32` and `i64` and `u32` are completely different - they even cannot be added together. This is very similar to Rust.
|
All types are treated strictly separate by Rhai, meaning that `i32` and `i64` and `u32` are completely different -
|
||||||
|
they even cannot be added together. This is very similar to Rust.
|
||||||
|
|
||||||
The default integer type is `i64`. If other integer types are not needed, it is possible to exclude them and make a smaller build with the [`only_i64`] feature.
|
The default integer type is `i64`. If other integer types are not needed, it is possible to exclude them and make a
|
||||||
|
smaller build with the [`only_i64`] feature.
|
||||||
|
|
||||||
If only 32-bit integers are needed, enabling the [`only_i32`] feature will remove support for all integer types other than `i32`, including `i64`.
|
If only 32-bit integers are needed, enabling the [`only_i32`] feature will remove support for all integer types other than `i32`,
|
||||||
This is useful on some 32-bit systems where using 64-bit integers incurs a performance penalty.
|
including `i64`. This is useful on some 32-bit systems where using 64-bit integers incurs a performance penalty.
|
||||||
|
|
||||||
If no floating-point is needed or supported, use the [`no_float`] feature to remove it.
|
If no floating-point is needed or supported, use the [`no_float`] feature to remove it.
|
||||||
|
|
||||||
@ -358,12 +364,13 @@ if type_of(mystery) == "i64" {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
In Rust, sometimes a `Dynamic` forms part of the return value - a good example is elements within an `Array` which are `Dynamic`,
|
In Rust, sometimes a `Dynamic` forms part of a returned value - a good example is an array with `Dynamic` elements,
|
||||||
or property values in an object map. In order to get the _real_ value, the actual value type _must_ be known in advance.
|
or an object map with `Dynamic` property values. To get the _real_ values, the actual value types _must_ be known in advance.
|
||||||
There is no easy way for Rust to detect, at run-time, what type the `Dynamic` value is (short of using the `type_name`
|
There is no easy way for Rust to decide, at run-time, what type the `Dynamic` value is (short of using the `type_name`
|
||||||
function to get the textual name of the type and then matching on that).
|
function and match against the name).
|
||||||
|
|
||||||
To use a `Dynamic` value in Rust, use the `cast` method to convert the value into a specific, known type.
|
A `Dynamic` value's actual type can be checked via the `is` method.
|
||||||
|
The `cast` method (from the `rhai::AnyExt` trait) then converts the value into a specific, known type.
|
||||||
Alternatively, use the `try_cast` method which does not panic but returns an error when the cast fails.
|
Alternatively, use the `try_cast` method which does not panic but returns an error when the cast fails.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
@ -372,13 +379,15 @@ use rhai::AnyExt; // Pull in the trait.
|
|||||||
let list: Array = engine.eval("...")?; // return type is 'Array'
|
let list: Array = engine.eval("...")?; // return type is 'Array'
|
||||||
let item = list[0]; // an element in an 'Array' is 'Dynamic'
|
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
|
||||||
|
|
||||||
let value = item.cast::<i64>(); // if the element is 'i64', this succeeds; otherwise it panics
|
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: 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 an error
|
let value = item.try_cast::<i64>()?; // 'try_cast' does not panic when the cast fails, but returns an error
|
||||||
```
|
```
|
||||||
|
|
||||||
The `type_name` method gets the name of the actual type as a string, which you may match against.
|
The `type_name` method gets the name of the actual type as a static string slice, which you may match against.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use rhai::Any; // Pull in the trait.
|
use rhai::Any; // Pull in the trait.
|
||||||
@ -423,7 +432,7 @@ To call these functions, they need to be registered with the [`Engine`].
|
|||||||
```rust
|
```rust
|
||||||
use rhai::{Engine, EvalAltResult};
|
use rhai::{Engine, EvalAltResult};
|
||||||
use rhai::RegisterFn; // use `RegisterFn` trait for `register_fn`
|
use rhai::RegisterFn; // use `RegisterFn` trait for `register_fn`
|
||||||
use rhai::{Dynamic, RegisterDynamicFn}; // use `RegisterDynamicFn` trait for `register_dynamic_fn`
|
use rhai::{Any, Dynamic, RegisterDynamicFn}; // use `RegisterDynamicFn` trait for `register_dynamic_fn`
|
||||||
|
|
||||||
// Normal function
|
// Normal function
|
||||||
fn add(x: i64, y: i64) -> i64 {
|
fn add(x: i64, y: i64) -> i64 {
|
||||||
@ -432,7 +441,7 @@ fn add(x: i64, y: i64) -> i64 {
|
|||||||
|
|
||||||
// Function that returns a Dynamic value
|
// Function that returns a Dynamic value
|
||||||
fn get_an_any() -> Dynamic {
|
fn get_an_any() -> Dynamic {
|
||||||
Box::new(42_i64)
|
(42_i64).into_dynamic() // 'into_dynamic' is defined by the 'rhai::Any' trait
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<(), EvalAltResult>
|
fn main() -> Result<(), EvalAltResult>
|
||||||
@ -456,14 +465,17 @@ fn main() -> Result<(), EvalAltResult>
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
To return a [`Dynamic`] value from a Rust function, simply `Box` it and return it.
|
To return a [`Dynamic`] value from a Rust function, use the `into_dynamic()` method
|
||||||
|
(under the `rhai::Any` trait) to convert it.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use rhai::Any; // Pull in the trait
|
||||||
|
|
||||||
fn decide(yes_no: bool) -> Dynamic {
|
fn decide(yes_no: bool) -> Dynamic {
|
||||||
if yes_no {
|
if yes_no {
|
||||||
Box::new(42_i64)
|
(42_i64).into_dynamic()
|
||||||
} else {
|
} else {
|
||||||
Box::new("hello world!".to_string()) // remember &str is not supported
|
String::from("hello!").into_dynamic() // remember &str is not supported by Rhai
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -471,7 +483,8 @@ fn decide(yes_no: bool) -> Dynamic {
|
|||||||
Generic functions
|
Generic functions
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
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.
|
||||||
|
Essentially this is a form of function overloading as Rhai does not support generics.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
@ -492,15 +505,17 @@ fn main()
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
This example shows how to register multiple functions (or, in this case, multiple instances of the same function) to the same name in script.
|
This example shows how to register multiple functions (or, in this case, multiple overloaded versions of the same function)
|
||||||
This enables function overloading based on the number and types of parameters.
|
under the same name. This enables function overloading based on the number and types of parameters.
|
||||||
|
|
||||||
Fallible functions
|
Fallible functions
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
If a function is _fallible_ (i.e. it returns a `Result<_, Error>`), it can be registered with `register_result_fn` (using the `RegisterResultFn` trait).
|
If a function is _fallible_ (i.e. it returns a `Result<_, Error>`), it can be registered with `register_result_fn`
|
||||||
|
(using the `RegisterResultFn` trait).
|
||||||
|
|
||||||
The function must return `Result<_, EvalAltResult>`. `EvalAltResult` implements `From<&str>` and `From<String>` etc. and the error text gets converted into `EvalAltResult::ErrorRuntime`.
|
The function must return `Result<_, EvalAltResult>`. `EvalAltResult` implements `From<&str>` and `From<String>` etc.
|
||||||
|
and the error text gets converted into `EvalAltResult::ErrorRuntime`.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use rhai::{Engine, EvalAltResult, Position};
|
use rhai::{Engine, EvalAltResult, Position};
|
||||||
@ -612,14 +627,15 @@ let mut engine = Engine::new();
|
|||||||
engine.register_type::<TestStruct>();
|
engine.register_type::<TestStruct>();
|
||||||
```
|
```
|
||||||
|
|
||||||
To use methods and functions with the [`Engine`], we need to register them. There are some convenience functions to help with this.
|
To use native types, methods and functions with the [`Engine`], we need to register them.
|
||||||
Below I register update and new with the [`Engine`].
|
There are some convenience functions to help with these. Below, the `update` and `new` methods are registered with the [`Engine`].
|
||||||
|
|
||||||
*Note: [`Engine`] follows the convention that methods use a `&mut` first parameter so that invoking methods can update the value in memory.*
|
*Note: [`Engine`] follows the convention that methods use a `&mut` first parameter so that invoking methods
|
||||||
|
can update the value in memory.*
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
engine.register_fn("update", TestStruct::update); // registers 'update(&mut ts)'
|
engine.register_fn("update", TestStruct::update); // registers 'update(&mut TestStruct)'
|
||||||
engine.register_fn("new_ts", TestStruct::new); // registers 'new'
|
engine.register_fn("new_ts", TestStruct::new); // registers 'new()'
|
||||||
```
|
```
|
||||||
|
|
||||||
Finally, we call our script. The script can see the function and method we registered earlier.
|
Finally, we call our script. The script can see the function and method we registered earlier.
|
||||||
@ -631,8 +647,9 @@ let result = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?;
|
|||||||
println!("result: {}", result.field); // prints 42
|
println!("result: {}", result.field); // prints 42
|
||||||
```
|
```
|
||||||
|
|
||||||
In fact, any function with a first argument (either by copy or via a `&mut` reference) can be used as a method-call on that type because internally they are the same thing:
|
In fact, any function with a first argument (either by copy or via a `&mut` reference) can be used as a method call
|
||||||
methods on a type is implemented as a functions taking an first argument.
|
on that type because internally they are the same thing:
|
||||||
|
methods on a type is implemented as a functions taking a `&mut` first argument.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn foo(ts: &mut TestStruct) -> i64 {
|
fn foo(ts: &mut TestStruct) -> i64 {
|
||||||
@ -646,7 +663,8 @@ let result = engine.eval::<i64>("let x = new_ts(); x.foo()")?;
|
|||||||
println!("result: {}", result); // prints 1
|
println!("result: {}", result); // prints 1
|
||||||
```
|
```
|
||||||
|
|
||||||
If the [`no_object`] feature is turned on, however, the _method_ style of function calls (i.e. calling a function as an object-method) is no longer supported.
|
If the [`no_object`] feature is turned on, however, the _method_ style of function calls
|
||||||
|
(i.e. calling a function as an object-method) is no longer supported.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Below is a syntax error under 'no_object' because 'len' cannot be called in method style.
|
// Below is a syntax error under 'no_object' because 'len' cannot be called in method style.
|
||||||
@ -709,19 +727,21 @@ println!("Answer: {}", result); // prints 42
|
|||||||
Needless to say, `register_type`, `register_type_with_name`, `register_get`, `register_set` and `register_get_set`
|
Needless to say, `register_type`, `register_type_with_name`, `register_get`, `register_set` and `register_get_set`
|
||||||
are not available when the [`no_object`] feature is turned on.
|
are not available when the [`no_object`] feature is turned on.
|
||||||
|
|
||||||
Initializing and maintaining state
|
`Scope` - Initializing and maintaining state
|
||||||
---------------------------------
|
-------------------------------------------
|
||||||
|
|
||||||
[`Scope`]: #initializing-and-maintaining-state
|
[`Scope`]: #scope---initializing-and-maintaining-state
|
||||||
|
|
||||||
By default, Rhai treats each [`Engine`] invocation as a fresh one, persisting only the functions that have been defined but no global state.
|
By default, Rhai treats each [`Engine`] invocation as a fresh one, persisting only the functions that have been defined
|
||||||
This gives each evaluation a clean starting slate. In order to continue using the same global state from one invocation to the next,
|
but no global state. This gives each evaluation a clean starting slate. In order to continue using the same global state
|
||||||
such a state must be manually created and passed in.
|
from one invocation to the next, such a state must be manually created and passed in.
|
||||||
|
|
||||||
All `Scope` variables are [`Dynamic`], meaning they can store values of any type. If the [`sync`] feature is used, however, then only types
|
All `Scope` variables are [`Dynamic`], meaning they can store values of any type. If the [`sync`] feature is used, however,
|
||||||
that are `Send + Sync` are supported, and the entire `Scope` itself will also be `Send + Sync`. This is extremely useful in multi-threaded applications.
|
then only types that are `Send + Sync` are supported, and the entire `Scope` itself will also be `Send + Sync`.
|
||||||
|
This is extremely useful in multi-threaded applications.
|
||||||
|
|
||||||
In this example, a global state object (a `Scope`) is created with a few initialized variables, then the same state is threaded through multiple invocations:
|
In this example, a global state object (a `Scope`) is created with a few initialized variables, then the same state is
|
||||||
|
threaded through multiple invocations:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use rhai::{Engine, Scope, EvalAltResult};
|
use rhai::{Engine, Scope, EvalAltResult};
|
||||||
@ -828,7 +848,8 @@ Variables
|
|||||||
|
|
||||||
Variables in Rhai follow normal C naming rules (i.e. must contain only ASCII letters, digits and underscores '`_`').
|
Variables in Rhai follow normal C naming rules (i.e. must contain only ASCII letters, digits and underscores '`_`').
|
||||||
|
|
||||||
Variable names must start with an ASCII letter or an underscore '`_`', must contain at least one ASCII letter, and must start with an ASCII letter before a digit.
|
Variable names must start with an ASCII letter or an underscore '`_`', must contain at least one ASCII letter,
|
||||||
|
and must start with an ASCII letter before a digit.
|
||||||
Therefore, names like '`_`', '`_42`', '`3a`' etc. are not legal variable names, but '`_c3po`' and '`r2d2`' are.
|
Therefore, names like '`_`', '`_42`', '`3a`' etc. are not legal variable names, but '`_c3po`' and '`r2d2`' are.
|
||||||
Variable names are also case _sensitive_.
|
Variable names are also case _sensitive_.
|
||||||
|
|
||||||
@ -879,7 +900,8 @@ Integer numbers follow C-style format with support for decimal, binary ('`0b`'),
|
|||||||
|
|
||||||
The default system integer type (also aliased to `INT`) is `i64`. It can be turned into `i32` via the [`only_i32`] feature.
|
The default system integer type (also aliased to `INT`) is `i64`. It can be turned into `i32` via the [`only_i32`] feature.
|
||||||
|
|
||||||
Floating-point numbers are also supported if not disabled with [`no_float`]. The default system floating-point type is `i64` (also aliased to `FLOAT`).
|
Floating-point numbers are also supported if not disabled with [`no_float`]. The default system floating-point type is `i64`
|
||||||
|
(also aliased to `FLOAT`).
|
||||||
|
|
||||||
'`_`' separators can be added freely and are ignored within a number.
|
'`_`' separators can be added freely and are ignored within a number.
|
||||||
|
|
||||||
@ -935,7 +957,8 @@ number = -5 - +5;
|
|||||||
Numeric functions
|
Numeric functions
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
The following standard functions (defined in the standard library but excluded if [`no_stdlib`]) operate on `i8`, `i16`, `i32`, `i64`, `f32` and `f64` only:
|
The following standard functions (defined in the standard library but excluded if [`no_stdlib`]) operate on
|
||||||
|
`i8`, `i16`, `i32`, `i64`, `f32` and `f64` only:
|
||||||
|
|
||||||
| Function | Description |
|
| Function | Description |
|
||||||
| ------------ | --------------------------------- |
|
| ------------ | --------------------------------- |
|
||||||
@ -961,18 +984,21 @@ The following standard functions (defined in the standard library but excluded i
|
|||||||
Strings and Chars
|
Strings and Chars
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
String and char literals follow C-style formatting, with support for Unicode ('`\u`_xxxx_' or '`\U`_xxxxxxxx_') and hex ('`\x`_xx_') escape sequences.
|
String and char literals follow C-style formatting, with support for Unicode ('`\u`_xxxx_' or '`\U`_xxxxxxxx_') and
|
||||||
|
hex ('`\x`_xx_') escape sequences.
|
||||||
|
|
||||||
Hex sequences map to ASCII characters, while '`\u`' maps to 16-bit common Unicode code points and '`\U`' maps the full, 32-bit extended Unicode code points.
|
Hex sequences map to ASCII characters, while '`\u`' maps to 16-bit common Unicode code points and '`\U`' maps the full,
|
||||||
|
32-bit extended Unicode code points.
|
||||||
|
|
||||||
Internally Rhai strings are stored as UTF-8 just like Rust (they _are_ Rust `String`s!), but there are major differences.
|
Internally Rhai strings are stored as UTF-8 just like Rust (they _are_ Rust `String`s!), but there are major differences.
|
||||||
In Rhai a string is the same as an array of Unicode characters and can be directly indexed (unlike Rust).
|
In Rhai a string is the same as an array of Unicode characters and can be directly indexed (unlike Rust).
|
||||||
This is similar to most other languages where strings are internally represented not as UTF-8 but as arrays of multi-byte Unicode characters.
|
This is similar to most other languages where strings are internally represented not as UTF-8 but as arrays of multi-byte
|
||||||
|
Unicode characters.
|
||||||
Individual characters within a Rhai string can also be replaced just as if the string is an array of Unicode characters.
|
Individual characters within a Rhai string can also be replaced just as if the string is an array of Unicode characters.
|
||||||
In Rhai, there is also no separate concepts of `String` and `&str` as in Rust.
|
In Rhai, there is also no separate concepts of `String` and `&str` as in Rust.
|
||||||
|
|
||||||
Strings can be built up from other strings and types via the `+` operator (provided by the standard library but excluded if [`no_stdlib`]).
|
Strings can be built up from other strings and types via the `+` operator (provided by the standard library but excluded
|
||||||
This is particularly useful when printing output.
|
if [`no_stdlib`]). This is particularly useful when printing output.
|
||||||
|
|
||||||
[`type_of()`] a string returns `"string"`.
|
[`type_of()`] a string returns `"string"`.
|
||||||
|
|
||||||
@ -1236,8 +1262,8 @@ Comparison operators
|
|||||||
|
|
||||||
Comparing most values of the same data type work out-of-the-box for standard types supported by the system.
|
Comparing most values of the same data type work out-of-the-box for standard types supported by the system.
|
||||||
|
|
||||||
However, if the [`no_stdlib`] feature is turned on, comparisons can only be made between restricted system
|
However, if the [`no_stdlib`] feature is turned on, comparisons can only be made between restricted system types -
|
||||||
types - `INT` (`i64` or `i32` depending on [`only_i32`] and [`only_i64`]), `f64` (if not [`no_float`]), string, array, `bool`, `char`.
|
`INT` (`i64` or `i32` depending on [`only_i32`] and [`only_i64`]), `f64` (if not [`no_float`]), string, array, `bool`, `char`.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
42 == 42; // true
|
42 == 42; // true
|
||||||
@ -1421,8 +1447,8 @@ return 123 + 456; // returns 579
|
|||||||
Errors and `throw`-ing exceptions
|
Errors and `throw`-ing exceptions
|
||||||
--------------------------------
|
--------------------------------
|
||||||
|
|
||||||
All of [`Engine`]'s evaluation/consuming methods return `Result<T, rhai::EvalAltResult>` with `EvalAltResult` holding error information.
|
All of [`Engine`]'s evaluation/consuming methods return `Result<T, rhai::EvalAltResult>` with `EvalAltResult`
|
||||||
To deliberately return an error during an evaluation, use the `throw` keyword.
|
holding error information. To deliberately return an error during an evaluation, use the `throw` keyword.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
if some_bad_condition_has_happened {
|
if some_bad_condition_has_happened {
|
||||||
@ -1493,7 +1519,8 @@ fn foo() { x } // <- syntax error: variable 'x' doesn't exist
|
|||||||
### Passing arguments by value
|
### Passing arguments by value
|
||||||
|
|
||||||
Functions defined in script always take [`Dynamic`] parameters (i.e. the parameter can be of any type).
|
Functions defined in script always take [`Dynamic`] parameters (i.e. the parameter can be of any type).
|
||||||
It is important to remember that all arguments are passed by _value_, so all functions are _pure_ (i.e. they never modify their arguments).
|
It is important to remember that all arguments are passed by _value_, so all functions are _pure_
|
||||||
|
(i.e. they never modifytheir arguments).
|
||||||
Any update to an argument will **not** be reflected back to the caller. This can introduce subtle bugs, if not careful.
|
Any update to an argument will **not** be reflected back to the caller. This can introduce subtle bugs, if not careful.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
@ -1621,8 +1648,8 @@ For example, in the following:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Rhai attempts to eliminate _dead code_ (i.e. code that does nothing, for example an expression by itself as a statement, which is allowed in Rhai).
|
Rhai attempts to eliminate _dead code_ (i.e. code that does nothing, for example an expression by itself as a statement,
|
||||||
The above script optimizes to:
|
which is allowed in Rhai). The above script optimizes to:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
{
|
{
|
||||||
@ -1750,21 +1777,22 @@ Function side effect considerations
|
|||||||
----------------------------------
|
----------------------------------
|
||||||
|
|
||||||
All of Rhai's built-in functions (and operators which are implemented as functions) are _pure_ (i.e. they do not mutate state
|
All of Rhai's built-in functions (and operators which are implemented as functions) are _pure_ (i.e. they do not mutate state
|
||||||
nor cause side any effects, with the exception of `print` and `debug` which are handled specially) so using [`OptimizationLevel::Full`]
|
nor cause side any effects, with the exception of `print` and `debug` which are handled specially) so using
|
||||||
is usually quite safe _unless_ you register your own types and functions.
|
[`OptimizationLevel::Full`] is usually quite safe _unless_ you register your own types and functions.
|
||||||
|
|
||||||
If custom functions are registered, they _may_ be called (or maybe not, if the calls happen to lie within a pruned code block).
|
If custom functions are registered, they _may_ be called (or maybe not, if the calls happen to lie within a pruned code block).
|
||||||
If custom functions are registered to replace built-in operators, they will also be called when the operators are used (in an `if`
|
If custom functions are registered to replace built-in operators, they will also be called when the operators are used
|
||||||
statement, for example) and cause side-effects.
|
(in an `if` statement, for example) and cause side-effects.
|
||||||
|
|
||||||
Function volatility considerations
|
Function volatility considerations
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
|
||||||
Even if a custom function does not mutate state nor cause side effects, it may still be _volatile_, i.e. it _depends_ on the external
|
Even if a custom function does not mutate state nor cause side effects, it may still be _volatile_, i.e. it _depends_
|
||||||
environment and is not _pure_. A perfect example is a function that gets the current time - obviously each run will return a different value!
|
on the external environment and is not _pure_. A perfect example is a function that gets the current time -
|
||||||
The optimizer, when using [`OptimizationLevel::Full`], _assumes_ that all functions are _pure_, so when it finds constant arguments
|
obviously each run will return a different value! The optimizer, when using [`OptimizationLevel::Full`], _assumes_ that
|
||||||
it will eagerly execute the function call. This causes the script to behave differently from the intended semantics because
|
all functions are _pure_, so when it finds constant arguments it will eagerly execute the function call.
|
||||||
essentially the result of the function call will always be the same value.
|
This causes the script to behave differently from the intended semantics because essentially the result of the function call
|
||||||
|
will always be the same value.
|
||||||
|
|
||||||
Therefore, **avoid using [`OptimizationLevel::Full`]** if you intend to register non-_pure_ custom types and/or functions.
|
Therefore, **avoid using [`OptimizationLevel::Full`]** if you intend to register non-_pure_ custom types and/or functions.
|
||||||
|
|
||||||
@ -1799,15 +1827,15 @@ print("start!");
|
|||||||
print("end!");
|
print("end!");
|
||||||
```
|
```
|
||||||
|
|
||||||
In the script above, if `my_decision` holds anything other than a boolean value, the script should have been terminated due to a type error.
|
In the script above, if `my_decision` holds anything other than a boolean value, the script should have been terminated due to
|
||||||
However, after optimization, the entire `if` statement is removed (because an access to `my_decision` produces no side effects),
|
a type error. However, after optimization, the entire `if` statement is removed (because an access to `my_decision` produces
|
||||||
thus the script silently runs to completion without errors.
|
no side effects), thus the script silently runs to completion without errors.
|
||||||
|
|
||||||
Turning off optimizations
|
Turning off optimizations
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
It is usually a bad idea to depend on a script failing or such kind of subtleties, but if it turns out to be necessary (why? I would never guess),
|
It is usually a bad idea to depend on a script failing or such kind of subtleties, but if it turns out to be necessary
|
||||||
turn it off by setting the optimization level to [`OptimizationLevel::None`].
|
(why? I would never guess), turn it off by setting the optimization level to [`OptimizationLevel::None`].
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let engine = rhai::Engine::new();
|
let engine = rhai::Engine::new();
|
||||||
|
218
src/scope.rs
218
src/scope.rs
@ -52,9 +52,14 @@ pub(crate) struct EntryRef<'a> {
|
|||||||
/// let mut engine = Engine::new();
|
/// let mut engine = Engine::new();
|
||||||
/// let mut my_scope = Scope::new();
|
/// let mut my_scope = Scope::new();
|
||||||
///
|
///
|
||||||
/// engine.eval_with_scope::<()>(&mut my_scope, "let x = 5;")?;
|
/// my_scope.push("z", 40_i64);
|
||||||
///
|
///
|
||||||
/// assert_eq!(engine.eval_with_scope::<i64>(&mut my_scope, "x + 1")?, 6);
|
/// engine.eval_with_scope::<()>(&mut my_scope, "let x = z + 1; z = 0;")?;
|
||||||
|
///
|
||||||
|
/// assert_eq!(engine.eval_with_scope::<i64>(&mut my_scope, "x + 1")?, 42);
|
||||||
|
///
|
||||||
|
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 41);
|
||||||
|
/// assert_eq!(my_scope.get_value::<i64>("z").unwrap(), 0);
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
@ -68,31 +73,106 @@ pub struct Scope<'a>(Vec<Entry<'a>>);
|
|||||||
|
|
||||||
impl<'a> Scope<'a> {
|
impl<'a> Scope<'a> {
|
||||||
/// Create a new Scope.
|
/// Create a new Scope.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rhai::Scope;
|
||||||
|
///
|
||||||
|
/// let mut my_scope = Scope::new();
|
||||||
|
///
|
||||||
|
/// my_scope.push("x", 42_i64);
|
||||||
|
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42);
|
||||||
|
/// ```
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self(Vec::new())
|
Self(Vec::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Empty the Scope.
|
/// Empty the Scope.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rhai::Scope;
|
||||||
|
///
|
||||||
|
/// let mut my_scope = Scope::new();
|
||||||
|
///
|
||||||
|
/// my_scope.push("x", 42_i64);
|
||||||
|
/// assert!(my_scope.contains("x"));
|
||||||
|
/// assert_eq!(my_scope.len(), 1);
|
||||||
|
/// assert!(!my_scope.is_empty());
|
||||||
|
///
|
||||||
|
/// my_scope.clear();
|
||||||
|
/// assert!(!my_scope.contains("x"));
|
||||||
|
/// assert_eq!(my_scope.len(), 0);
|
||||||
|
/// assert!(my_scope.is_empty());
|
||||||
|
/// ```
|
||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
self.0.clear();
|
self.0.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the number of entries inside the Scope.
|
/// Get the number of entries inside the Scope.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rhai::Scope;
|
||||||
|
///
|
||||||
|
/// let mut my_scope = Scope::new();
|
||||||
|
/// assert_eq!(my_scope.len(), 0);
|
||||||
|
///
|
||||||
|
/// my_scope.push("x", 42_i64);
|
||||||
|
/// assert_eq!(my_scope.len(), 1);
|
||||||
|
/// ```
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.0.len()
|
self.0.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Is the Scope empty?
|
/// Is the Scope empty?
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rhai::Scope;
|
||||||
|
///
|
||||||
|
/// let mut my_scope = Scope::new();
|
||||||
|
/// assert!(my_scope.is_empty());
|
||||||
|
///
|
||||||
|
/// my_scope.push("x", 42_i64);
|
||||||
|
/// assert!(!my_scope.is_empty());
|
||||||
|
/// ```
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.0.len() == 0
|
self.0.len() == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add (push) a new entry to the Scope.
|
/// Add (push) a new entry to the Scope.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rhai::Scope;
|
||||||
|
///
|
||||||
|
/// let mut my_scope = Scope::new();
|
||||||
|
///
|
||||||
|
/// my_scope.push("x", 42_i64);
|
||||||
|
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42);
|
||||||
|
/// ```
|
||||||
pub fn push<K: Into<Cow<'a, str>>, T: Any + Clone>(&mut self, name: K, value: T) {
|
pub fn push<K: Into<Cow<'a, str>>, T: Any + Clone>(&mut self, name: K, value: T) {
|
||||||
self.push_dynamic_value(name, EntryType::Normal, value.into_dynamic(), false);
|
self.push_dynamic_value(name, EntryType::Normal, value.into_dynamic(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add (push) a new `Dynamic` entry to the Scope.
|
/// Add (push) a new `Dynamic` entry to the Scope.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rhai::{Any, Scope};
|
||||||
|
///
|
||||||
|
/// let mut my_scope = Scope::new();
|
||||||
|
///
|
||||||
|
/// my_scope.push_dynamic("x", (42_i64).into_dynamic());
|
||||||
|
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42);
|
||||||
|
/// ```
|
||||||
pub fn push_dynamic<K: Into<Cow<'a, str>>>(&mut self, name: K, value: Dynamic) {
|
pub fn push_dynamic<K: Into<Cow<'a, str>>>(&mut self, name: K, value: Dynamic) {
|
||||||
self.push_dynamic_value(name, EntryType::Normal, value, false);
|
self.push_dynamic_value(name, EntryType::Normal, value, false);
|
||||||
}
|
}
|
||||||
@ -101,8 +181,20 @@ impl<'a> Scope<'a> {
|
|||||||
///
|
///
|
||||||
/// Constants are immutable and cannot be assigned to. Their values never change.
|
/// Constants are immutable and cannot be assigned to. Their values never change.
|
||||||
/// Constants propagation is a technique used to optimize an AST.
|
/// Constants propagation is a technique used to optimize an AST.
|
||||||
|
///
|
||||||
/// However, in order to be used for optimization, constants must be in one of the recognized types:
|
/// However, in order to be used for optimization, constants must be in one of the recognized types:
|
||||||
/// `INT` (default to `i64`, `i32` if `only_i32`), `f64`, `String`, `char` and `bool`.
|
/// `INT` (default to `i64`, `i32` if `only_i32`), `f64`, `String`, `char` and `bool`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rhai::Scope;
|
||||||
|
///
|
||||||
|
/// let mut my_scope = Scope::new();
|
||||||
|
///
|
||||||
|
/// my_scope.push_constant("x", 42_i64);
|
||||||
|
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42);
|
||||||
|
/// ```
|
||||||
pub fn push_constant<K: Into<Cow<'a, str>>, T: Any + Clone>(&mut self, name: K, value: T) {
|
pub fn push_constant<K: Into<Cow<'a, str>>, T: Any + Clone>(&mut self, name: K, value: T) {
|
||||||
self.push_dynamic_value(name, EntryType::Constant, value.into_dynamic(), true);
|
self.push_dynamic_value(name, EntryType::Constant, value.into_dynamic(), true);
|
||||||
}
|
}
|
||||||
@ -111,9 +203,21 @@ impl<'a> Scope<'a> {
|
|||||||
///
|
///
|
||||||
/// Constants are immutable and cannot be assigned to. Their values never change.
|
/// Constants are immutable and cannot be assigned to. Their values never change.
|
||||||
/// Constants propagation is a technique used to optimize an AST.
|
/// Constants propagation is a technique used to optimize an AST.
|
||||||
|
///
|
||||||
/// However, in order to be used for optimization, the `Dynamic` value must be in one of the
|
/// However, in order to be used for optimization, the `Dynamic` value must be in one of the
|
||||||
/// recognized types:
|
/// recognized types:
|
||||||
/// `INT` (default to `i64`, `i32` if `only_i32`), `f64`, `String`, `char` and `bool`.
|
/// `INT` (default to `i64`, `i32` if `only_i32`), `f64`, `String`, `char` and `bool`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rhai::{Any, Scope};
|
||||||
|
///
|
||||||
|
/// let mut my_scope = Scope::new();
|
||||||
|
///
|
||||||
|
/// my_scope.push_constant_dynamic("x", (42_i64).into_dynamic());
|
||||||
|
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42);
|
||||||
|
/// ```
|
||||||
pub fn push_constant_dynamic<K: Into<Cow<'a, str>>>(&mut self, name: K, value: Dynamic) {
|
pub fn push_constant_dynamic<K: Into<Cow<'a, str>>>(&mut self, name: K, value: Dynamic) {
|
||||||
self.push_dynamic_value(name, EntryType::Constant, value, true);
|
self.push_dynamic_value(name, EntryType::Constant, value, true);
|
||||||
}
|
}
|
||||||
@ -139,63 +243,129 @@ impl<'a> Scope<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Truncate (rewind) the Scope to a previous size.
|
/// Truncate (rewind) the Scope to a previous size.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rhai::Scope;
|
||||||
|
///
|
||||||
|
/// let mut my_scope = Scope::new();
|
||||||
|
///
|
||||||
|
/// my_scope.push("x", 42_i64);
|
||||||
|
/// my_scope.push("y", 123_i64);
|
||||||
|
/// assert!(my_scope.contains("x"));
|
||||||
|
/// assert!(my_scope.contains("y"));
|
||||||
|
/// assert_eq!(my_scope.len(), 2);
|
||||||
|
///
|
||||||
|
/// my_scope.rewind(1);
|
||||||
|
/// assert!(my_scope.contains("x"));
|
||||||
|
/// assert!(!my_scope.contains("y"));
|
||||||
|
/// assert_eq!(my_scope.len(), 1);
|
||||||
|
///
|
||||||
|
/// my_scope.rewind(0);
|
||||||
|
/// assert!(!my_scope.contains("x"));
|
||||||
|
/// assert!(!my_scope.contains("y"));
|
||||||
|
/// assert_eq!(my_scope.len(), 0);
|
||||||
|
/// assert!(my_scope.is_empty());
|
||||||
|
/// ```
|
||||||
pub fn rewind(&mut self, size: usize) {
|
pub fn rewind(&mut self, size: usize) {
|
||||||
self.0.truncate(size);
|
self.0.truncate(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Does the scope contain the entry?
|
/// Does the scope contain the entry?
|
||||||
pub fn contains(&self, key: &str) -> bool {
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rhai::Scope;
|
||||||
|
///
|
||||||
|
/// let mut my_scope = Scope::new();
|
||||||
|
///
|
||||||
|
/// my_scope.push("x", 42_i64);
|
||||||
|
/// assert!(my_scope.contains("x"));
|
||||||
|
/// assert!(!my_scope.contains("y"));
|
||||||
|
/// ```
|
||||||
|
pub fn contains(&self, name: &str) -> bool {
|
||||||
self.0
|
self.0
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
|
||||||
.rev() // Always search a Scope in reverse order
|
.rev() // Always search a Scope in reverse order
|
||||||
.any(|(_, Entry { name, .. })| name == key)
|
.any(|Entry { name: key, .. }| name == key)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find an entry in the Scope, starting from the last.
|
/// Find an entry in the Scope, starting from the last.
|
||||||
pub(crate) fn get(&self, key: &str) -> Option<(EntryRef, Dynamic)> {
|
pub(crate) fn get(&self, name: &str) -> Option<(EntryRef, Dynamic)> {
|
||||||
self.0
|
self.0
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.rev() // Always search a Scope in reverse order
|
.rev() // Always search a Scope in reverse order
|
||||||
.find(|(_, Entry { name, .. })| name == key)
|
.find_map(
|
||||||
.map(
|
|
||||||
|(
|
|(
|
||||||
index,
|
index,
|
||||||
Entry {
|
Entry {
|
||||||
name, typ, value, ..
|
name: key,
|
||||||
|
typ,
|
||||||
|
value,
|
||||||
|
..
|
||||||
},
|
},
|
||||||
)| {
|
)| {
|
||||||
(
|
if name == key {
|
||||||
EntryRef {
|
Some((
|
||||||
name,
|
EntryRef {
|
||||||
index,
|
name: key,
|
||||||
typ: *typ,
|
index,
|
||||||
},
|
typ: *typ,
|
||||||
value.clone(),
|
},
|
||||||
)
|
value.clone(),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the value of an entry in the Scope, starting from the last.
|
/// Get the value of an entry in the Scope, starting from the last.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rhai::Scope;
|
||||||
|
///
|
||||||
|
/// let mut my_scope = Scope::new();
|
||||||
|
///
|
||||||
|
/// my_scope.push("x", 42_i64);
|
||||||
|
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42);
|
||||||
|
/// ```
|
||||||
pub fn get_value<T: Any + Clone>(&self, name: &str) -> Option<T> {
|
pub fn get_value<T: Any + Clone>(&self, name: &str) -> Option<T> {
|
||||||
self.0
|
self.0
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.rev()
|
||||||
.rev() // Always search a Scope in reverse order
|
.find(|Entry { name: key, .. }| name == key)
|
||||||
.find(|(_, Entry { name: key, .. })| name == key)
|
.and_then(|Entry { value, .. }| value.downcast_ref::<T>())
|
||||||
.and_then(|(_, Entry { value, .. })| value.downcast_ref::<T>())
|
|
||||||
.map(T::clone)
|
.map(T::clone)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the value of the named variable.
|
/// Update the value of the named entry.
|
||||||
/// Search starts from the last, and only the last variable matching the specified name is updated.
|
/// Search starts backwards from the last, and only the first entry matching the specified name is updated.
|
||||||
/// If no variable matching the specified name is found, a new variable is added.
|
/// If no entry matching the specified name is found, a new one is added.
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Panics when trying to update the value of a constant.
|
/// Panics when trying to update the value of a constant.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rhai::Scope;
|
||||||
|
///
|
||||||
|
/// let mut my_scope = Scope::new();
|
||||||
|
///
|
||||||
|
/// my_scope.push("x", 42_i64);
|
||||||
|
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42);
|
||||||
|
///
|
||||||
|
/// my_scope.set_value("x", 0_i64);
|
||||||
|
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 0);
|
||||||
|
/// ```
|
||||||
pub fn set_value<T: Any + Clone>(&mut self, name: &'a str, value: T) {
|
pub fn set_value<T: Any + Clone>(&mut self, name: &'a str, value: T) {
|
||||||
match self.get(name) {
|
match self.get(name) {
|
||||||
Some((
|
Some((
|
||||||
|
Loading…
Reference in New Issue
Block a user