# RFC: Switch expression

- Status: Withdrawn
- Authors: Ilija Tovilo, tovilo.ilija@gmail.com
- Date: 2020-03-28
- Target: PHP 8.0
- Implementation: https://github.com/php/php-src/pull/5308
- Original: https://wiki.php.net/rfc/switch_expression
- Page: https://externals.io/rfc/switch_expression
- More for agents: search with https://externals.io/search.md?q={query} · RFCs at https://externals.io/rfc.md · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

## Discussions on the mailing list

- [\[RFC\]\[DISCUSSION\] switch expression](https://externals.io/message/109383.md) — started on 2020-03-28, 5 messages
- [\[RFC\]\[POLL\] Switch expression](https://externals.io/message/109487.md) — started on 2020-03-31, 8 messages
- [\[DISCUSSION\] Match expression](https://externals.io/message/109590.md) — started on 2020-04-12, 36 messages

---

## Introduction

The `switch` statement has some long-standing issues that we're going to look at in this RFC.

- Returning values
- Fallthrough
- Inexhaustiveness
- Type coercion

## Proposal

This RFC proposes to introduce an expression variant of the `switch` statement that addresses some of the issues mentioned above.

```php
$expressionResult = switch ($condition) {
    1 => foo(),
    2 => bar(),
    3, 4, 5 => baz(),
};
```

It also suggests allowing multiple case conditions implemented for the `switch` expression in the statement.

```php
switch ($condition) {
    case 1:
        foo();
        break;
    case 2:
        bar();
        break;
    case 3, 4, 5:
        baz();
        break;
}
```

## Issues

We're going to take a look at each issue and how we can improve the `switch` statement or expression in that regard.

### Returning values

It is very common that the `switch` produces some value that is used after the `switch` statement.

```php
switch ($x) {
    case 0:
        $y = 'Foo';
        break;
    case 1:
        $y = 'Bar';
        break;
    case 2:
        $y = 'Baz';
        break;
}
 
var_dump($y);
```

It is easy to forget assigning `$y` in one of the cases. It is also visually unintuitive to find `$y` declared in a deeper nested scope. This is the main motivation for introducing a `switch` expression that allows returning values from cases in a more natural way.

```php
$y = switch ($x) {
    0 => 'Foo',
    1 => 'Bar',
    2 => 'Baz',
};
 
var_dump($y);
```

### Fallthrough

The `switch` fallthrough has been a large source of bugs in many languages. Each `case` must explicitely `break` out of the `switch` statement or the execution will continue into the `case` even if the condition is not met.

```php
switch ($pressedKey) {
    case Key::ENTER:
        save();
        // Oops, forgot the break
    case Key::DELETE:
        delete();
        break;
}
```

This was intended to be a feature so that multiple conditions can execute the same block of code.

```php
switch ($x) {
    case 1:
    case 2:
        // Same for 1 and 2
        break;
    case 3:
        // Only 3
    case 4:
        // Same for 3 and 4
}
```

It is often hard to understand if the missing `break` was the authors intention or a mistake. Many modern languages avoid this issue by implicitly breaking out of the `case`. Multiple conditions can be provided to the same `case` so that they execute the same block. There's often no way to achieve the same result as 3 and 4 in the example above without an additional `if` statement.

```php
switch ($x) {
    case 1, 2:
        // Same for 1 and 2
    case 3, 4:
        if ($x === 3) {
            // Only 3
        }
        // Same for 3 and 4
}
```

The fallthrough behavior can't reasonably be changed in the `switch` statement because it would break a lot of code. However this RFC porposes allowing multiple conditions per `case` so that the intention of running the same code can be expressed more clearly. The `switch` expression resolves this issue exactly as described above. There is an implicit `break` added after each `case`. Like with the statement multiple `case` conditions can be separated by a comma.

### Inexhaustiveness

Another large source of bugs is not handling all the possible cases supplied to the `switch` statement.

```php
switch ($x) {
    case 1:
        // ...
        break;
    case 2:
        // ...
        break;
}
 
// $x is a 3? I never expected a 3
```

The unexpected value will go unnoticed until the program crashes in a weird way, causes strange behavior or even worse becomes a security hole. Many languages can check if all the cases are handled at compile time or force you to write a `default` case if they can't. For a dynamic language like PHP the only alternative is throwing an error. We can't reasonably change the exhaustiveness behavior in the `switch` statement because it would break a lot of code. The `switch` expression resolves this issue by throwing an `UnhandledSwitchCaseError` if the condition isn't met for any of the cases and the `switch` doesn't contain a `default` case.

```php
switch ($x) {
    1 => ...,
    2 => ...,
};
 
// $x can never be 3
```

### Type coercion

The `switch` statement loosely compares the given value to the case values. This can lead to some very unexpected results.

```php
switch ('foo') {
    case 0:
      echo "Oh no!\n";
      break;
}
```

It is very tempting to fix this issue for the `switch` expression. This RFC proposes not to do so as it would add an arbitrary distinction between the `switch` statement and expression. Hopefully this is something that can be addressed in the future for both the statement and expression (see chapter “Fixing the statement”).

## Fixing the statement

There is a proposal to introduce [editions to PHP](https://github.com/php/php-rfcs/pull/2) that allow for bigger backward incompatible changes. This would be a perfect opportunity to fix the undesirable behavior in the `switch` statement. This is, however, not a part of this RFC.

## Expression syntax

There is an ambiguity problem with the empty `switch` statement vs expression:

```php
// Could be a switch expression or a switch statement with an empty statement (;)
switch ($x) {};
```

To resolve it ambiguity empty switch expressions are not disallowed.

```php
// This code throws a parser error
$x = switch ($y) {};
```

## "Why don't you just use x"

There have been some comments on how you can already achieve the same result.

### if statements

```php
if ($x === 1) {
    $y = ...;
} elseif ($x === 2) {
    $y = ...;
} elseif ($x === 3) {
    $y = ...;
}
```

Needless to say this is incredibly verbose and there's a lot of repetition. It also can't make use of the switches jumptable optimization. You must also not forget to write an `else` statement to catch unwanted values.

### Hash maps

```php
$y = [
    1 => ...,
    2 => ...,
][$x];
```

This code will execute every single “case”, not just the one that is finally chosen. It will also build a hash map in memory every time the `switch` is executed. And again, you must not forget to handle unwanted values.

### Nested ternary operators

```php
$y = $x === 1 ? ...
  : ($x === 2 ? ...
  : ($x === 3 ? ...
  : 0));
```

The parentheses make it hard to read and it's easy to make mistakes and there is no jumptable optimization. Adding more cases will make the situation worse.

## Future scope

As mentioned each `case` in the `switch` expression can only contain a single expression. We could allow passing blocks to the `case` in the future but this is not part of this RFC.

```php
echo switch ($x) {
    1 => {
        foo();
        bar();
        baz() // Rust style return value by omitting semicolon
    },
};
```

## Backward Incompatible Changes

There are no breaking changes in this RFC.

## Proposed PHP Version(s)

The proposed version is PHP 8.

## Proposed Voting Choices

As this is a language change, a 2/3 majority is required. The vote is a straight Yes/No vote for accepting the RFC and merging the patch.
