Add docs and tests for switch.

This commit is contained in:
Stephen Chung 2020-11-14 09:38:16 +08:00
parent b0c66eb5e5
commit 83c7c101d1
5 changed files with 155 additions and 14 deletions

View File

@ -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)

View File

@ -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
View 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;
```

View File

@ -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.

View File

@ -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(())
}
}