216 lines
6.2 KiB
Markdown
216 lines
6.2 KiB
Markdown
Switch Statement
|
|
================
|
|
|
|
{{#include ../links.md}}
|
|
|
|
The `switch` statement allows matching on [literal] values, and it mostly follows Rust's `match` syntax.
|
|
|
|
```js
|
|
switch calc_secret_value(x) {
|
|
1 => print("It's one!"),
|
|
2 => {
|
|
// A statements block instead of a one-line statement
|
|
print("It's two!");
|
|
print("Again!");
|
|
}
|
|
3 => print("Go!"),
|
|
// A list of alternatives
|
|
4 | 5 | 6 => print("Some small number!"),
|
|
// _ is the default when no case matches. It must be the last case.
|
|
_ => print(`Oops! Something's wrong: ${x}`)
|
|
}
|
|
```
|
|
|
|
|
|
Default Case
|
|
------------
|
|
|
|
A _default_ case (i.e. when no other cases match) can be specified with `_`.
|
|
|
|
```admonish warning.small "Must be last"
|
|
|
|
The default case must be the _last_ case in the `switch` statement.
|
|
```
|
|
|
|
```js
|
|
switch wrong_default {
|
|
1 => 2,
|
|
_ => 9, // <- syntax error: default case not the last
|
|
2 => 3,
|
|
3 => 4, // <- ending with extra comma is OK
|
|
}
|
|
|
|
switch wrong_default {
|
|
1 => 2,
|
|
2 => 3,
|
|
3 => 4,
|
|
_ => 8, // <- syntax error: default case not the last
|
|
_ => 9
|
|
}
|
|
```
|
|
|
|
|
|
Array and Object Map Literals Also Work
|
|
---------------------------------------
|
|
|
|
The `switch` expression can match against any _[literal]_, including [array] and [object map] [literals].
|
|
|
|
```js
|
|
// Match on arrays
|
|
switch [foo, bar, baz] {
|
|
["hello", 42, true] => ...,
|
|
["hello", 123, false] => ...,
|
|
["world", 1, true] => ...,
|
|
_ => ...
|
|
}
|
|
|
|
// Match on object maps
|
|
switch map {
|
|
#{ a: 1, b: 2, c: true } => ...,
|
|
#{ a: 42, d: "hello" } => ...,
|
|
_ => ...
|
|
}
|
|
```
|
|
|
|
```admonish tip.small "Tip: Working with enums"
|
|
|
|
Switching on [arrays] is very useful when working with Rust enums
|
|
(see [this section]({{rootUrl}}/patterns/enums.md) for more details).
|
|
```
|
|
|
|
|
|
Case Conditions
|
|
---------------
|
|
|
|
Similar to Rust, each case (except the default case at the end) can provide an optional condition
|
|
that must evaluate to `true` in order for the case to match.
|
|
|
|
All cases are checked in order, so an earlier case that matches will override all later cases.
|
|
|
|
```js
|
|
let result = switch calc_secret_value(x) {
|
|
1 if some_external_condition(x, y, z) => 100,
|
|
|
|
1 | 2 | 3 if x < foo => 200, // <- all alternatives share the same condition
|
|
|
|
2 if bar() => 999,
|
|
|
|
2 => "two", // <- fallback value for 2
|
|
|
|
2 => "dead code", // <- this case is a duplicate and will never match
|
|
// because the previous case matches first
|
|
|
|
5 if CONDITION => 123, // <- value for 5 matching condition
|
|
|
|
5 => "five", // <- fallback value for 5
|
|
|
|
_ if CONDITION => 8888 // <- syntax error: default case cannot have condition
|
|
};
|
|
```
|
|
|
|
~~~admonish tip "Tip: Use with `type_of()`"
|
|
|
|
Case conditions, together with [`type_of()`], makes it extremely easy to work with
|
|
values which may be of several different types (like properties in a JSON object).
|
|
|
|
```js
|
|
switch value.type_of() {
|
|
// if 'value' is a string...
|
|
"string" if value.len() < 5 => ...,
|
|
"string" => ...,
|
|
|
|
// if 'value' is an array...
|
|
"array" => ...,
|
|
|
|
// if 'value' is an object map...
|
|
"map" if value.prop == 42 => ...,
|
|
"map" => ...,
|
|
|
|
// if 'value' is a number...
|
|
"i64" if value > 0 => ...,
|
|
"i64" => ...,
|
|
|
|
// anything else: probably an error...
|
|
_ => ...
|
|
}
|
|
```
|
|
~~~
|
|
|
|
|
|
Range Cases
|
|
-----------
|
|
|
|
Because of their popularity, [literal] integer [ranges] can also be used as `switch` cases.
|
|
|
|
Numeric [ranges] are only searched when the `switch` value is itself a number (including
|
|
floating-point and [`Decimal`][rust_decimal]). They never match any other data types.
|
|
|
|
```admonish warning.small "Must come after numeric cases"
|
|
|
|
Range cases must come _after_ all numeric cases.
|
|
```
|
|
|
|
```js
|
|
let x = 42;
|
|
|
|
switch x {
|
|
'x' => ..., // no match: wrong data type
|
|
|
|
1 => ..., // <- specific numeric cases are checked first
|
|
2 => ..., // <- but these do not match
|
|
|
|
0..50 if x > 45 => ..., // no match: condition is 'false'
|
|
|
|
-10..20 => ..., // no match: not in range
|
|
|
|
0..50 => ..., // <- MATCH!!!
|
|
|
|
30..100 => ..., // no match: even though it is within range,
|
|
// the previous case matches first
|
|
|
|
42 => ..., // <- syntax error: numeric cases cannot follow range cases
|
|
}
|
|
```
|
|
|
|
```admonish tip.small "Tip: Ranges can overlap"
|
|
|
|
When more then one [range] contain the `switch` value, the _first_ one with a fulfilled condition
|
|
(if any) is evaluated.
|
|
|
|
Numeric [range] cases are tried in the order that they appear in the original script.
|
|
```
|
|
|
|
|
|
Difference From `if`-`else if` Chain
|
|
------------------------------------
|
|
|
|
Although a `switch` expression looks _almost_ the same as an [`if`-`else if`][`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 if`][`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 `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 statements via a pre-compiled look-up table. This makes
|
|
matching extremely efficient, but it also means that [overloading][operator overloading] the `==`
|
|
operator will have no effect.
|
|
|
|
Therefore, in environments where it is desirable to [overload][operator overloading] the `==`
|
|
operator for [standard types] – 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`][`if`] chain becomes increasingly slower with each additional case because
|
|
essentially an O(n) _linear scan_ is performed.
|