From 83c7c101d11c963da48a4089a062f0b58967fa61 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 14 Nov 2020 09:38:16 +0800 Subject: [PATCH] Add docs and tests for switch. --- doc/src/SUMMARY.md | 15 ++++----- doc/src/language/switch.md | 26 ++++++++++++---- doc/src/patterns/enums.md | 64 ++++++++++++++++++++++++++++++++++++++ doc/src/rust/custom.md | 7 +++++ tests/switch.rs | 57 ++++++++++++++++++++++++++++++++- 5 files changed, 155 insertions(+), 14 deletions(-) create mode 100644 doc/src/patterns/enums.md diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md index f0ee7b6a..8bdce36f 100644 --- a/doc/src/SUMMARY.md +++ b/doc/src/SUMMARY.md @@ -117,13 +117,14 @@ The Rhai Scripting Language 6. [Subtle Semantic Changes](engine/optimize/semantics.md) 8. [Usage Patterns](patterns/index.md) 1. [Object-Oriented Programming (OOP)](patterns/oop.md) - 2. [Loadable Configuration](patterns/config.md) - 3. [Control Layer](patterns/control.md) - 4. [Singleton Command](patterns/singleton.md) - 5. [Multi-Layer Functions](patterns/multi-layer.md) - 6. [One Engine Instance Per Call](patterns/parallel.md) - 7. [Scriptable Event Handler with State](patterns/events.md) - 8. [Dynamic Constants Provider](patterns/dynamic-const.md) + 2. [Working With Rust Enums](patterns/enums.md) + 3. [Loadable Configuration](patterns/config.md) + 4. [Control Layer](patterns/control.md) + 5. [Singleton Command](patterns/singleton.md) + 6. [Multi-Layer Functions](patterns/multi-layer.md) + 7. [One Engine Instance Per Call](patterns/parallel.md) + 8. [Scriptable Event Handler with State](patterns/events.md) + 9. [Dynamic Constants Provider](patterns/dynamic-const.md) 9. [Advanced Topics](advanced.md) 1. [Capture Scope for Function Call](language/fn-capture.md) 2. [Low-Level API](rust/register-raw.md) diff --git a/doc/src/language/switch.md b/doc/src/language/switch.md index 45b9385d..2d05787c 100644 --- a/doc/src/language/switch.md +++ b/doc/src/language/switch.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. +### Look-up Table vs `x == y` + A `switch` expression matches through _hashing_ via a look-up table. -Therefore, matching is very fast. Walking down an `if`-`else` chain -will be _much_ slower. +Therefore, matching is very fast. Walking down an `if`-`else if` chain +is _much_ slower. On the other hand, operators can be [overloaded][operator overloading] in Rhai, 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; 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] 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. + +### 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. diff --git a/doc/src/patterns/enums.md b/doc/src/patterns/enums.md new file mode 100644 index 00000000..c5663b44 --- /dev/null +++ b/doc/src/patterns/enums.md @@ -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") + .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; +``` diff --git a/doc/src/rust/custom.md b/doc/src/rust/custom.md index 4c187d78..36394e3a 100644 --- a/doc/src/rust/custom.md +++ b/doc/src/rust/custom.md @@ -191,3 +191,10 @@ engine.register_fn("==", let item = new_ts(); // construct a new 'TestStruct' 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. diff --git a/tests/switch.rs b/tests/switch.rs index 64fe9d31..9e6dcad2 100644 --- a/tests/switch.rs +++ b/tests/switch.rs @@ -1,4 +1,4 @@ -use rhai::{Engine, EvalAltResult, Scope, INT}; +use rhai::{Dynamic, Engine, EvalAltResult, Scope, INT}; #[test] fn test_switch() -> Result<(), Box> { @@ -62,3 +62,58 @@ fn test_switch() -> Result<(), Box> { 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> { + let mut engine = Engine::new(); + + engine + .register_type_with_name::("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::( + &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(()) + } +}