Add docs and tests for switch.
This commit is contained in:
parent
b0c66eb5e5
commit
83c7c101d1
@ -117,13 +117,14 @@ The Rhai Scripting Language
|
|||||||
6. [Subtle Semantic Changes](engine/optimize/semantics.md)
|
6. [Subtle Semantic Changes](engine/optimize/semantics.md)
|
||||||
8. [Usage Patterns](patterns/index.md)
|
8. [Usage Patterns](patterns/index.md)
|
||||||
1. [Object-Oriented Programming (OOP)](patterns/oop.md)
|
1. [Object-Oriented Programming (OOP)](patterns/oop.md)
|
||||||
2. [Loadable Configuration](patterns/config.md)
|
2. [Working With Rust Enums](patterns/enums.md)
|
||||||
3. [Control Layer](patterns/control.md)
|
3. [Loadable Configuration](patterns/config.md)
|
||||||
4. [Singleton Command](patterns/singleton.md)
|
4. [Control Layer](patterns/control.md)
|
||||||
5. [Multi-Layer Functions](patterns/multi-layer.md)
|
5. [Singleton Command](patterns/singleton.md)
|
||||||
6. [One Engine Instance Per Call](patterns/parallel.md)
|
6. [Multi-Layer Functions](patterns/multi-layer.md)
|
||||||
7. [Scriptable Event Handler with State](patterns/events.md)
|
7. [One Engine Instance Per Call](patterns/parallel.md)
|
||||||
8. [Dynamic Constants Provider](patterns/dynamic-const.md)
|
8. [Scriptable Event Handler with State](patterns/events.md)
|
||||||
|
9. [Dynamic Constants Provider](patterns/dynamic-const.md)
|
||||||
9. [Advanced Topics](advanced.md)
|
9. [Advanced Topics](advanced.md)
|
||||||
1. [Capture Scope for Function Call](language/fn-capture.md)
|
1. [Capture Scope for Function Call](language/fn-capture.md)
|
||||||
2. [Low-Level API](rust/register-raw.md)
|
2. [Low-Level API](rust/register-raw.md)
|
||||||
|
@ -71,20 +71,25 @@ switch map {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Switching on [arrays] is very useful when working with Rust enums (see [this chapter]({{rootUrl}}/patterns/enums.md)
|
||||||
|
for more details).
|
||||||
|
|
||||||
Difference From If-Else Chain
|
|
||||||
-----------------------------
|
|
||||||
|
|
||||||
Although a `switch` expression looks _almost_ the same as an `if`-`else` chain,
|
Difference From `if` - `else if` Chain
|
||||||
|
-------------------------------------
|
||||||
|
|
||||||
|
Although a `switch` expression looks _almost_ the same as an `if`-`else if` chain,
|
||||||
there are subtle differences between the two.
|
there are subtle differences between the two.
|
||||||
|
|
||||||
|
### Look-up Table vs `x == y`
|
||||||
|
|
||||||
A `switch` expression matches through _hashing_ via a look-up table.
|
A `switch` expression matches through _hashing_ via a look-up table.
|
||||||
Therefore, matching is very fast. Walking down an `if`-`else` chain
|
Therefore, matching is very fast. Walking down an `if`-`else if` chain
|
||||||
will be _much_ slower.
|
is _much_ slower.
|
||||||
|
|
||||||
On the other hand, operators can be [overloaded][operator overloading] in Rhai,
|
On the other hand, operators can be [overloaded][operator overloading] in Rhai,
|
||||||
meaning that it is possible to override the `==` operator for integers such
|
meaning that it is possible to override the `==` operator for integers such
|
||||||
that `if x == y` returns a different result from the built-in default.
|
that `x == y` returns a different result from the built-in default.
|
||||||
|
|
||||||
`switch` expressions do _not_ use the `==` operator for comparison;
|
`switch` expressions do _not_ use the `==` operator for comparison;
|
||||||
instead, they _hash_ the data values and jump directly to the correct
|
instead, they _hash_ the data values and jump directly to the correct
|
||||||
@ -95,3 +100,12 @@ the `==` operator will have no effect.
|
|||||||
Therefore, in environments where it is desirable to [overload][operator overloading]
|
Therefore, in environments where it is desirable to [overload][operator overloading]
|
||||||
the `==` operator - though it is difficult to think of valid scenarios where you'd want
|
the `==` operator - though it is difficult to think of valid scenarios where you'd want
|
||||||
`1 == 1` to return something other than `true` - avoid using the `switch` expression.
|
`1 == 1` to return something other than `true` - avoid using the `switch` expression.
|
||||||
|
|
||||||
|
### Efficiency
|
||||||
|
|
||||||
|
Because the `switch` expression works through a look-up table, it is very efficient
|
||||||
|
even for _large_ number of cases; in fact, switching is an O(1) operation regardless
|
||||||
|
of the size of the data and number of cases to match.
|
||||||
|
|
||||||
|
A long `if`-`else if` chain becomes increasingly slower with each additional case
|
||||||
|
because essentially an O(n) _linear scan_ is performed.
|
||||||
|
64
doc/src/patterns/enums.md
Normal file
64
doc/src/patterns/enums.md
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
Working With Rust Enums
|
||||||
|
=======================
|
||||||
|
|
||||||
|
{{#include ../links.md}}
|
||||||
|
|
||||||
|
Enums in Rust are typically used with _pattern matching_. Rhai is dynamic, so although
|
||||||
|
it integrates with Rust enum variants just fine (treated transparently as [custom types]),
|
||||||
|
it is impossible (short of registering a complete API) to distinguish between individual
|
||||||
|
enum variants or to extract internal data from them.
|
||||||
|
|
||||||
|
|
||||||
|
Switch Through Arrays
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
An easy way to work with Rust enums is through exposing the internal data of each enum variant
|
||||||
|
as an [array], usually with the name of the variant as the first item:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use rhai::{Engine, Array};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum MyEnum {
|
||||||
|
Foo,
|
||||||
|
Bar(i64),
|
||||||
|
Baz(String, bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MyEnum {
|
||||||
|
fn get_enum_data(&mut self) -> Array {
|
||||||
|
match self {
|
||||||
|
Self::Foo => vec![
|
||||||
|
"Foo".into()
|
||||||
|
] as Array,
|
||||||
|
Self::Bar(num) => vec![
|
||||||
|
"Bar".into(), (*num).into()
|
||||||
|
] as Array,
|
||||||
|
Self::Baz(name, option) => vec![
|
||||||
|
"Baz".into(), name.clone().into(), (*option).into()
|
||||||
|
] as Array
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
engine
|
||||||
|
.register_type_with_name::<MyEnum>("MyEnum")
|
||||||
|
.register_get("enum_data", MyEnum::get_enum_data);
|
||||||
|
```
|
||||||
|
|
||||||
|
Then it is a simple matter to match an enum via the `switch` expression:
|
||||||
|
|
||||||
|
```c
|
||||||
|
// Assume 'value' = 'MyEnum::Baz("hello", true)'
|
||||||
|
// 'get_data' creates a variable-length array with 'MyEnum' data
|
||||||
|
let x = switch value.enum_data {
|
||||||
|
["Foo"] => 1,
|
||||||
|
["Bar", 42] => 2,
|
||||||
|
["Bar", 123] => 3,
|
||||||
|
["Baz", "hello", false] => 4,
|
||||||
|
["Baz", "hello", true] => 5,
|
||||||
|
_ => 9
|
||||||
|
};
|
||||||
|
|
||||||
|
x == 5;
|
||||||
|
```
|
@ -191,3 +191,10 @@ engine.register_fn("==",
|
|||||||
let item = new_ts(); // construct a new 'TestStruct'
|
let item = new_ts(); // construct a new 'TestStruct'
|
||||||
item in array; // 'in' operator uses '=='
|
item in array; // 'in' operator uses '=='
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Working With Enums
|
||||||
|
------------------
|
||||||
|
|
||||||
|
It is quite easy to use Rust enums with Rhai.
|
||||||
|
See [this chapter]({{rootUrl}}/patterns/enums.md) for more details.
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use rhai::{Engine, EvalAltResult, Scope, INT};
|
use rhai::{Dynamic, Engine, EvalAltResult, Scope, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_switch() -> Result<(), Box<EvalAltResult>> {
|
fn test_switch() -> Result<(), Box<EvalAltResult>> {
|
||||||
@ -62,3 +62,58 @@ fn test_switch() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
mod test_switch_enum {
|
||||||
|
use super::*;
|
||||||
|
use rhai::Array;
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum MyEnum {
|
||||||
|
Foo,
|
||||||
|
Bar(i64),
|
||||||
|
Baz(String, bool),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MyEnum {
|
||||||
|
fn get_enum_data(&mut self) -> Array {
|
||||||
|
match self {
|
||||||
|
Self::Foo => vec!["Foo".into()] as Array,
|
||||||
|
Self::Bar(num) => vec!["Bar".into(), (*num).into()] as Array,
|
||||||
|
Self::Baz(name, option) => {
|
||||||
|
vec!["Baz".into(), name.clone().into(), (*option).into()] as Array
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_switch_enum() -> Result<(), Box<EvalAltResult>> {
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
engine
|
||||||
|
.register_type_with_name::<MyEnum>("MyEnum")
|
||||||
|
.register_get("get_data", MyEnum::get_enum_data);
|
||||||
|
|
||||||
|
let mut scope = Scope::new();
|
||||||
|
scope.push("x", MyEnum::Baz("hello".to_string(), true));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval_with_scope::<INT>(
|
||||||
|
&mut scope,
|
||||||
|
r#"
|
||||||
|
switch x.get_data {
|
||||||
|
["Foo"] => 1,
|
||||||
|
["Bar", 42] => 2,
|
||||||
|
["Bar", 123] => 3,
|
||||||
|
["Baz", "hello", false] => 4,
|
||||||
|
["Baz", "hello", true] => 5,
|
||||||
|
_ => 9
|
||||||
|
}
|
||||||
|
"#
|
||||||
|
)?,
|
||||||
|
5
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user