245 lines
6.7 KiB
Markdown
245 lines
6.7 KiB
Markdown
Switch Statement
|
|
================
|
|
|
|
The `switch` statement allows matching on [literal](../appendix/literals.md) values.
|
|
|
|
```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](../appendix/literals.md)_, including
|
|
[array](arrays.md) and [object map](object-maps.md) [literals](../appendix/literals.md).
|
|
|
|
```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" } => ...,
|
|
_ => ...
|
|
}
|
|
```
|
|
|
|
|
|
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()`](type-of.md), 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](../appendix/literals.md) integer [ranges](ranges.md) can also
|
|
be used as `switch` cases.
|
|
|
|
Numeric [ranges](ranges.md) are only searched when the `switch` value is itself a number (including
|
|
floating-point and 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!!! duplicated range cases are OK
|
|
|
|
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](ranges.md) contain the `switch` value, the _first_ one with a fulfilled condition
|
|
(if any) is evaluated.
|
|
|
|
Numeric [range](ranges.md) cases are tried in the order that they appear in the original script.
|
|
```
|
|
|
|
|
|
Switch Expression
|
|
=================
|
|
|
|
Like [`if`](if.md), `switch` also works as an _expression_.
|
|
|
|
```admonish tip.small "Tip"
|
|
|
|
This means that a `switch` expression can appear anywhere a regular expression can,
|
|
e.g. as [function](functions.md) call arguments.
|
|
```
|
|
|
|
```js
|
|
let x = switch foo { 1 => true, _ => false };
|
|
|
|
func(switch foo {
|
|
"hello" => 42,
|
|
"world" => 123,
|
|
_ => 0
|
|
});
|
|
|
|
// The above is somewhat equivalent to:
|
|
|
|
let x = if foo == 1 { true } else { false };
|
|
|
|
if foo == "hello" {
|
|
func(42);
|
|
} else if foo == "world" {
|
|
func(123);
|
|
} else {
|
|
func(0);
|
|
}
|
|
```
|
|
|
|
|
|
Difference From `if`-`else if` Chain
|
|
------------------------------------
|
|
|
|
Although a `switch` expression looks _almost_ the same as an [`if`-`else if`](if.md) 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.md) chain is _much_ slower.
|
|
|
|
On the other hand, operators can be [overloaded](overload.md) 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](overload.md) the `==` operator
|
|
will have no effect.
|
|
|
|
Therefore, in environments where it is desirable to [overload](overload.md) the `==` operator for
|
|
[standard types](values-and-types.md) – 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.md) chain becomes increasingly slower with each additional case because
|
|
essentially an O(n) _linear scan_ is performed.
|