Switch Statements

switch provides ordered branching over either one subject value or a sequence of boolean conditions.

switch status {
case Status.Ready:
    Start()

case Status.Waiting:
    Wait()

default:
    Stop()
}

Sec supports both subject-based and subjectless switches. In both forms, cases are considered in source order and the first matching case wins.

Rule: switch is ordered branching, not structural pattern matching. Use match when variants must be destructured, payloads bound, exhaustiveness required, or a branch expression must produce a value.

Subject Switch

A subject switch evaluates one expression and compares cases against it:

switch expression {
case value:
    statements

case value1, value2:
    statements

case range:
    statements

case < limit:
    statements

default:
    statements
}

The body may contain zero or more case clauses and one optional final default clause.

Case items may be ordinary compatible values, ranges or relational forms.

Subjectless Switch

Omitting the subject creates an ordered chain of boolean conditions:

switch {
case score < 0:
    Invalid()

case score < 50:
    Failed()

case score < 80:
    Passed()

default:
    Excellent()
}

Every case item in a subjectless switch must have type bool. There is no implicit truthiness.

A subjectless switch is useful when several peer conditions are clearer than a long if / else if chain.

Subject Evaluation

The subject expression is evaluated exactly once.

switch ReadStatus() {
case Status.Ready:
    Start()

default:
    Stop()
}

ReadStatus() is called once. The resulting value is then used for all case tests.

The compiler may keep that value in an SSA value, machine register or temporary storage, but lowering must preserve the one-evaluation source semantics.

Case Evaluation Order

Cases are considered from top to bottom in source order.

Within one case, comma-separated alternatives are considered left to right.

switch value {
case first, second, third:
    Selected()

case other:
    Other()
}

The first matching case body executes.

After a case matches:

  • later case tests are not evaluated;
  • the selected case body executes once;
  • normal completion continues after the switch;
  • explicit fallthrough may instead enter the next case body.
Rule: Source order is semantically observable when case expressions have side effects or use runtime values.

Value Cases

A value case compares the subject using ordinary Sec equality semantics.

switch value {
case 1:
    One()

case 2, 3, 5, 7:
    Prime()

default:
    Other()
}

Comma-separated values are alternatives. The body executes when the subject equals any one of them.

Every case item must be equality-compatible with the subject. A switch does not introduce implicit conversions or relaxed type identity.

type CustomerID uint64
type ProductID uint64

let customer: CustomerID := 42
let product: ProductID := 42

switch customer {
case product:
    ...             // invalid
}

Dynamic Case Expressions

Case values are not restricted to compile-time constants.

switch value {
case minimum:
    AtMinimum()

case CalculateTarget():
    AtTarget()
}

Case expressions may include:

  • literals;
  • constants;
  • variables;
  • fields;
  • properties;
  • function calls;
  • method calls;
  • other expressions valid for equality comparison with the subject.

Dynamic case expressions are evaluated lazily only when execution reaches them.

switch value {
case KnownValue:
    HandleKnown()

case ExpensiveCheckValue():
    HandleExpensive()
}

If KnownValue matches, ExpensiveCheckValue() is not called.

Range Cases

A subject switch may test range membership directly:

switch score {
case 0..<50:
    Failed()

case 50..<80:
    Passed()

case 80..100:
    Excellent()
}

Range cases use the ordinary Sec range forms:

start..end
start..<end
start..
..end
..<end

The subject and the range bounds must have compatible ordered types.

A range case is a membership test. It does not construct a sequence, iterate over the range or reevaluate the subject.

Relational Cases

A subject switch may use an ordered comparison without repeating the subject:

switch value {
case < 0:
    Negative()

case 0:
    Zero()

case > 0:
    Positive()
}

Supported forms are:

case < expression:
case <= expression:
case > expression:
case >= expression:

This:

switch value {
case < limit:
    Below()
}

uses the same ordered-comparison semantics as:

if value < limit {
    Below()
}

The subject and the relational operand must support the requested ordered comparison.

Several relational alternatives

switch value {
case < minimum, > maximum:
    Outside()

default:
    Inside()
}

These alternatives mean OR, not conjunction. They are evaluated left to right.

Mixed Alternatives

A subject case may combine compatible values, ranges and relational items:

switch value {
case 1, 3, 5, 10..<20:
    Selected()

default:
    Other()
}

Each item is tested left to right. The body executes once when any item matches.

Every alternative must independently satisfy the type rules required by its form.

Subjectless Comma Alternatives

In a subjectless switch, comma-separated conditions behave like boolean OR with left-to-right short-circuit evaluation:

switch {
case user.IsAdmin(), user.IsOwner():
    Allow()

case user.IsBlocked():
    Deny()

default:
    Review()
}

Once one condition in a case becomes true, later alternatives in that case are skipped.

Once a case matches, every later case is skipped.

Rule: Subjectless comma alternatives are ordered, short-circuiting alternatives. They are not a set whose evaluation order may be rearranged.

Default

default executes only when no preceding case matches.

switch value {
case 1:
    One()

case 2:
    Two()

default:
    Other()
}

default is optional.

If no case matches and no default exists, execution continues after the switch.

A switch may contain at most one default, and it must be the final clause.

No Implicit Fallthrough

Ordinary cases do not continue into the following case.

switch value {
case 1:
    One()

case 2:
    Two()
}

When value is 1, only One() executes. Normal completion then continues after the switch.

There is no required break at the end of a normal case.

Explicit fallthrough

Use fallthrough when execution must continue directly into the next case body:

switch value {
case 1:
    One()
    fallthrough

case 2:
    OneOrTwo()

default:
    Other()
}

When value is 1:

  1. One() executes;
  2. fallthrough transfers directly to the next case body;
  3. the next case's tests are not evaluated;
  4. OneOrTwo() executes.

fallthrough must:

  • be the final non-empty statement in its case;
  • have a following case;
  • appear directly in the case body;
  • not appear in default.

A nested if, loop, lambda or other nested block cannot use fallthrough to target an enclosing switch case.

Empty Cases

An empty case is legal:

switch value {
case 1:

case 2:
    Two()
}

An empty case does not implicitly fall through. When value is 1, no statements execute and control continues after the switch.

Use explicit fallthrough if the next case body should execute.

Case Scope

Every case body has its own lexical scope.

switch value {
case 1:
    let message := "one"
    Print(message)

case 2:
    let message := "two"
    Print(message)
}

The two message bindings belong to independent sibling scopes.

A binding declared in one case is not visible in another case or after the switch.

fallthrough transfers control into the next body, but it does not merge the lexical scopes:

switch value {
case 1:
    let local := 10
    fallthrough

case 2:
    Use(local)    // invalid
}

Ownership, destruction and borrow analysis still account for the control-flow edge created by fallthrough.

Duplicate and Overlapping Cases

Sec diagnoses case selections that are provably duplicated or unreachable.

Duplicate compile-time values

switch value {
case 1:
    First()

case 1:
    Second()
}

This is invalid. The compiler does not silently rely on source order when two compile-time values are known to be equal.

Overlapping compile-time ranges

switch score {
case 0..50:
    First()

case 40..100:
    Second()
}

The ranges overlap and are invalid when the overlap is statically known.

A later exact value cannot be hidden inside an earlier compile-time range:

switch score {
case 0..100:
    InRange()

case 50:
    Exact()
}

Relational reachability

Partial overlap is valid when later values remain reachable:

switch value {
case <= -10:
    VeryLow()

case < 0:
    Low()
}

The second case still handles values from -9 through -1.

A fully covered later relational case is invalid:

switch value {
case >= 0:
    NonNegative()

case > 10:
    Large()
}

Dynamic overlap

If values or bounds are dynamic and the compiler cannot prove complete coverage, the cases remain valid. Runtime source order determines which matching case wins.

Enum Subjects

An enum may be used as a switch subject:

switch direction {
case Direction.North:
    North()

case Direction.South:
    South()

default:
    Other()
}

A switch over an enum is not required to be exhaustive.

Use match when exhaustive handling of the enum domain is part of the intended semantics.

For open foreign or hardware enum domains, the owning enum or FFI rules may require handling unknown values, often through default.

String Subjects

Strings may be switched on using ordinary string equality:

switch command {
case "start":
    Start()

case "stop":
    Stop()

default:
    Unknown()
}

switch does not introduce a special case-insensitive, locale-sensitive or otherwise altered string comparison.

Named Semantic Types and Units

Named types retain their ordinary compatibility rules inside a switch.

switch speed {
case minimumSpeed..maximumSpeed:
    Normal()
}

An unrelated primitive value does not become compatible merely because it is written in a case.

Unit-bearing values retain normal unit compatibility:

switch distance {
case 0<m>..<100<m>:
    Near()

case 100<m>..:
    Far()
}

Incompatible units cannot participate in the same equality, range or ordered comparison merely because the operation occurs inside switch.

Boolean Subjects

A boolean value may be used as an explicit subject:

switch ready {
case true:
    Start()

case false:
    Wait()
}

This is valid Sec. An if / else is often simpler when there is only one boolean distinction, but the language does not prohibit a boolean subject switch.

Switch Is Not Pattern Matching

switch does not destructure union, enum, Result or Option payloads.

Invalid:

switch result {
case Ok(value):
    Use(value)
}

Use match:

match result {
    Ok(value) => Use(value)
    Err(error) => Handle(error)
}

switch does not add:

  • wildcard patterns;
  • variant payload bindings;
  • property patterns;
  • list patterns;
  • type-binding patterns;
  • structural destructuring.
Boundary: Use switch for ordered tests. Use match for structural patterns, payloads and exhaustiveness.

No Switch Case Guards

Sec 0.1 does not add a separate where guard to switch cases.

Invalid:

switch value {
case 10..20 where user.Active:
    Handle()
}

Use a subjectless switch when the complete boolean condition should select the case:

switch {
case value in 10..20 && user.Active:
    Handle()
}

or place an if inside the selected case body.

where belongs to the guarded-pattern model of match, not to switch.

Switch Is a Statement

switch does not produce a value.

Invalid:

let result := switch value {
case 1:
    10

default:
    20
}

Use match when the branch construct itself should produce a value, or assign to an outer mutable binding:

let mut result: int

switch value {
case 1:
    result = 10

default:
    result = 20
}

Definite-assignment analysis verifies that every continuing path has assigned the value before it is read.

Break and Continue

A switch does not create a break or continue target.

Those statements retain their loop-only meaning.

switch value {
case 1:
    break    // invalid unless this statement belongs to an applicable enclosing loop context
}

Ordinary switch cases do not need break because there is no implicit fallthrough.

fallthrough is the only switch-specific case-to-case transfer statement.

Flow Analysis

Switch cases participate in ordinary path-sensitive control-flow, ownership, borrowing, cleanup and definite-assignment analysis.

Terminating switch

A switch terminates when every reachable execution path through it terminates rather than reaching the continuation after the statement.

fn Classify(value: int) int {
    switch value {
    case < 0:
        return -1

    case 0:
        return 0

    default:
        return 1
    }
}

This satisfies the function return requirement because every possible switch path returns.

A switch without complete coverage normally retains an unmatched path that continues after the statement.

Definite assignment

fn Select(value: int) int {
    let mut result: int

    switch value {
    case 1:
        result = 10

    case 2:
        result = 20

    default:
        result = 30
    }

    return result
}

This is valid because every continuing path assigns result.

Without complete coverage, the possible unmatched path must also be included in definite-assignment analysis.

Fallthrough chains

A fallthrough edge participates in the same flow merge, ownership, destruction and cleanup reasoning as other explicit control-flow edges.

Unreachable cases

When earlier cases provably cover every value that could reach a later case, the later case is unreachable and the compiler diagnoses it.

Lowering Semantics

The backend may choose different low-level representations for different switches. A constant integer switch may become a jump table, while another switch may become a decision tree or ordinary branches.

Every lowering strategy must preserve:

  • one evaluation of the subject;
  • source-order case testing where evaluation is observable;
  • left-to-right comma-alternative evaluation;
  • short-circuiting for subjectless comma alternatives;
  • first-match-wins semantics;
  • no implicit fallthrough;
  • direct body transfer for explicit fallthrough;
  • default only after all earlier tests fail;
  • case lexical scopes, destruction and cleanup edges.
Rule: Optimization may change the shape of generated control flow. It may not change observable switch evaluation semantics.

Best Practices

Use switch for ordered peer tests

A switch is useful when several comparisons against one subject or several peer boolean conditions read more clearly than nested conditional code.

Prefer ranges for interval meaning

switch score {
case 0..<50:
    Failed()

case 50..<80:
    Passed()

default:
    Excellent()
}

This usually communicates interval intent more directly than repeating lower and upper comparisons.

Prefer relational cases when they avoid repeated subject syntax

switch value {
case < 0:
    Negative()

case 0:
    Zero()

case > 0:
    Positive()
}

Use fallthrough sparingly

Use fallthrough only when the next case body must execute without reevaluating the next case's tests.

Do not use it to recreate C-style switch behavior.

Use match when structure matters

Prefer match when:

  • exhaustiveness is part of the intended contract;
  • variants or payloads must be bound;
  • structural patterns are required;
  • guards belong to patterns;
  • the branch construct should produce a value.

Summary

  • switch supports subject-based and subjectless forms.
  • A subject expression is evaluated exactly once.
  • Cases are tested from top to bottom in source order.
  • Comma-separated alternatives are tested left to right.
  • The first matching case wins.
  • Value cases use ordinary equality compatibility.
  • Case expressions may be dynamic and are evaluated lazily when reached.
  • Range cases use ordinary Sec range and ordered-type semantics.
  • Relational cases use <, <=, > and >= without repeating the subject.
  • Value, range and relational alternatives may be combined when compatible.
  • Every subjectless case item must have type bool.
  • Subjectless comma alternatives short-circuit like ordered OR alternatives.
  • default is optional, unique and final.
  • There is no implicit case fallthrough.
  • Explicit fallthrough enters the immediately following case body without testing that case.
  • An empty case does not imply fallthrough.
  • Every case body has its own lexical scope.
  • Fallthrough does not merge case scopes.
  • Duplicate compile-time values are invalid.
  • Provably overlapping compile-time ranges are invalid.
  • Fully covered later cases are unreachable and diagnosed.
  • Dynamic overlap remains valid when complete coverage cannot be proven.
  • Enums, strings, booleans, named semantic types and unit-bearing values may be switch subjects when their ordinary operations permit it.
  • switch does not provide general pattern matching.
  • Switch case where guards are not part of Sec 0.1.
  • switch is a statement and does not produce a value.
  • break and continue remain loop-control statements.
  • Switch termination and definite assignment are path-sensitive.
  • Backend optimizations must preserve source-order and first-match semantics where observable.