# switch expression

> Choose a VALUE by matching a subject against patterns. Arms are tried in order and the first match wins. Patterns are a constant, a relational comparison, a type test, or the `_` discard. Over a closed vocabulary — an enum or a bool — a missing arm is a compile error rather than a runtime throw.

<!-- id: function-switch-expression · area: function · stability: stable · html: https://osysharp.com/reference/function/switch-expression/ -->

## Summary        {#summary}
`subject switch { … }` is an EXPRESSION: it produces a value. Arms are tried top to bottom and the first
one that matches wins. It is the form to reach for wherever a value is wanted rather than a statement —
notably inside a render block, which takes no statements.

Over a **closed vocabulary** (an `enum`, or a `bool`) a switch expression with no `_` arm must cover every
member, and a gap is a **compile error**.

## Signature      {#signature}
```osy syntax
subject switch {
  Member        => value,     // a CONSTANT — an enum member or a literal
  >= 1000.0     => value,     // a RELATIONAL comparison: < <= > >=
  Circle c      => value,     // a TYPE pattern, with a required binding
  _             => value,     // the DISCARD — matches anything
}
```

## Description    {#description}
**Arms are tried in order, and order is the whole design of a relational ladder.** `1500000` satisfies both
`>= 1000000` and `>= 1000`; writing the larger bound first is what makes it answer `"M"`.

**A relational arm compares against a constant** and may use any of `<`, `<=`, `>`, `>=`. There is no `==`
form because a bare constant arm already means equals.

**Exhaustiveness is checked only where a vocabulary is closed.** An `enum` and a `bool` have a knowable set
of values, so every one must have an arm or the compile fails. A number does not, so a switch over one
always needs `_` — including a relational ladder, because proving that `< 0` and `>= 0` between them cover
every number is arithmetic the compiler does not attempt. It asks for a `_` you may not strictly need
rather than claiming a gap is covered when it is not.

> **A missing enum arm is an ERROR, not a warning.** This is a deliberate divergence from C#, which warns at
> compile time and throws at run time. A throw inside a render expression is a blank page instead of a
> message, so the check that prevents it has to be the one that cannot be ignored.

**The subject is read once per arm test**, so it must be re-readable at no cost — a name, a member access,
an index. Anything that could do work (a call, an `await`) is refused, and the refusal names the one-line
fix: assign it to a local first.

A `when` guard is not part of this form. Use an `if` for that.

## Examples       {#examples}
```osy title="a relational ladder, an enum switch, and a type pattern" test app=switch-expression-examples
public enum MarkKind { Line, Area, Column }

// RELATIONAL — the compact-number ladder. The larger bound comes first because the first match wins.
string Compact(int n) {
  return n switch { >= 1000000 => "M", >= 1000 => "K", _ => "" };
}

// All four ordering operators.
string Band(int n) {
  return n switch { < 0 => "neg", <= 10 => "small", > 100 => "big", _ => "mid" };
}

// A CONSTANT arm over an enum. No `_`: every member has an arm, which is what makes adding a member to
// the vocabulary a build failure at each site that must decide about it.
string Family(MarkKind k) {
  return k switch { Line => "stroke", Area => "stroke", Column => "fill" };
}

// A bool is a closed vocabulary too, so both cases are exhaustive with no `_`.
string YesNo(int n) { return (n > 0) switch { true => "yes", false => "no" }; }
```

```osy title="a TYPE pattern binds the narrowed value" test app=switch-expression-types
class Shape { public string Name; }
class Circle : Shape { public double Radius; }

// `Circle c` binds `c` at the narrower type, so `c.Radius` reads inside that arm.
string Describe(Shape s) {
  return s switch { Circle c => $"circle r={c.Radius}", _ => s.Name };
}
```

## See also       {#see-also}
- [switch](https://osysharp.com/reference/function/switch/) — the switch STATEMENT, which branches control rather than producing a value
- [if / else](https://osysharp.com/reference/function/if/) — the two-way conditional, and where a `when`-guard-shaped test belongs
- [Testing which class a value is](https://osysharp.com/reference/class/type-tests/) — `is` / `as` / a cast / `OfType<T>()`, the other places a type is asked about
- [Enums](https://osysharp.com/reference/enum/index/) — why a closed vocabulary is what makes exhaustiveness checkable
