# switch expression

> Choose a VALUE by matching a subject against patterns. Arms are tried in order and the first match wins. An arm is any C# pattern — a constant, a relational comparison, a type test, `_`, or a property, list, positional or combined pattern — optionally with a `when` guard. 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, binding the narrowed value
  Square        => value,     // a TYPE pattern with no binding (C# 9)
  { X: > 0 }    => value,     // any other PATTERN — see function-patterns
  var n when n > 5 => value,  // a `when` guard that must also hold
  _             => 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 type pattern may bind or not.** `Circle c => …` names the subject at the narrower type for that arm;
`Square => …` only tests it. A bare name is read as a type when the subject is a row or an object, no local
of that name is in scope, and the name is a type — otherwise it is a constant, as before. Both forms test the
row's real type, so a row read through its base type (a kit's `Case`) answers by the subtype it is.

**Any pattern is an arm, and an arm may carry a `when` guard** — `{ Y: var y } when y < 0 => "below"`. The
forms are on [Patterns](https://osysharp.com/reference/function/patterns/). A switch that uses one (or a guard) has no exhaustiveness check, because the
compiler cannot prove a gap over a shape: write `_`, or a value that matches no arm throws
`InvalidOperationException`, as C#'s does.

## 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; }
class Square : Shape { public double Side; }

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

## See also       {#see-also}
- [switch](https://osysharp.com/reference/function/switch/) — the switch STATEMENT, which branches control rather than producing a value
- [Patterns](https://osysharp.com/reference/function/patterns/) — every pattern an arm may be, and the names they bind
- [if / else](https://osysharp.com/reference/function/if/) — the two-way conditional
- [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
