Switch Statements
A switch statement selects one branch from an ordered set of cases. It is intended for
value-based branching and may either compare one subject expression against several cases or evaluate
boolean conditions directly when no subject is present.
Cases are tested from top to bottom, and the first matching case is selected. After the selected case
finishes, execution continues after the switch unless the case returns, otherwise terminates, or uses
an explicit fallthrough statement.
switch is for value-based branching. Structural matching,
destructuring, union variants, Result and Option belong to match.
Switch with a Subject
The most common form evaluates one subject expression and compares it with one or more case items.
switch expression {
case value:
statements
case otherValue:
statements
default:
statements
}
Example:
switch status {
case Status.ready:
Start()
case Status.waiting:
Wait()
case Status.failed:
Stop()
default:
Reset()
}
The switch body is enclosed by braces. Each case begins with case, followed by one or
more case items and a colon. The optional default clause handles every value that did
not match an earlier case.
Subject Evaluation
The subject expression is evaluated exactly once.
switch ReadStatus() {
case Status.ready:
Start()
default:
Stop()
}
In this example, ReadStatus() is called once. Its result is then used for all case
comparisons. This rule is important when the subject expression performs work, reads mutable state
or has visible side effects.
Case Order
Cases are tested in source order. The first matching case wins. Once a case has matched, later case expressions are not evaluated.
switch value {
case minimum:
AtMinimum()
case CalculateTarget():
AtTarget()
default:
Other()
}
CalculateTarget() is only called when the first case does not match. Case order may
therefore affect both program behavior and performance.
Value Cases
A value case compares the switch subject with a compatible case expression.
switch value {
case 1:
One()
case 2:
Two()
default:
Other()
}
Each case value must be equality-compatible with the subject type. Sec does not perform unrelated implicit conversions merely to make a case compile.
let value: int := 1
switch value {
case "one":
One()
} // Error: switch case must be compatible with subject type int, got string.
Case Expressions
Case items are not limited to literals. A case expression may be a:
- literal,
- constant,
- variable,
- field or property,
- function call,
- method call,
- or another expression compatible with the subject.
switch value {
case minimum:
AtMinimum()
case limits.Maximum:
AtMaximum()
case CalculateTarget():
AtTarget()
}
Case expressions are evaluated only if execution reaches their case. Expressions in one case are evaluated from left to right.
Multiple Values in One Case
Several alternatives may share one case body by separating them with commas.
switch value {
case 2, 3, 5, 7:
Prime()
default:
Other()
}
The case above is equivalent to testing:
value == 2 || value == 3 || value == 5 || value == 7
Alternatives are tested from left to right. The body runs once when any alternative matches.
Range Cases
Ranges may be used directly as case items.
switch score {
case 0..<50:
Failed()
case 50..<80:
Passed()
case 80..100:
Excellent()
}
Normal Sec range forms are supported:
start..end
start..<end
start..
..end
..<end
The subject and the range bounds must have compatible ordered types. Inclusive and exclusive range boundaries retain their normal meaning.
switch temperature {
case ..<0:
Frozen()
case 0..<100:
Liquid()
case 100..:
BoilingOrAbove()
}
Values and ranges may be combined in the same case:
switch value {
case 1, 3, 5, 10..<20:
Selected()
default:
Other()
}
Relational Cases
A relational case compares the switch subject without repeating it. The supported operators are
<, <=, > and >=.
switch value {
case < 0:
Negative()
case 0:
Zero()
case > 0:
Positive()
}
The following case:
case < limit:
means:
subject < limit
Several relational alternatives may share one body:
switch value {
case < minimum, > maximum:
Outside()
default:
Inside()
}
Relational cases require an ordered subject type and an operand compatible with that type.
Subjectless Switch
A switch without a subject evaluates boolean conditions. It is useful when several peer conditions should be tested in order.
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. No implicit truthiness is
applied.
switch {
case 10:
Invalid()
} // Error: subjectless switch case must be bool, got int.
Comma-separated conditions use short-circuit logical OR:
switch {
case user.IsAdmin(), user.IsOwner():
Allow()
case user.IsBlocked():
Deny()
default:
Review()
}
A subjectless switch behaves similarly to an if/else if chain. The switch
form is often clearer when all branches are conceptually equal cases of one decision.
default
The optional default clause runs when no case matches. A switch may contain at most one
default, and it must be the final clause.
switch value {
case 1:
One()
case 2:
Two()
default:
Other()
}
If no case matches and no default exists, the switch performs no branch body and
execution continues after the switch.
switch value {
default:
Other()
case 1:
One()
} // Error: default must be the final switch clause.
No Implicit Fallthrough
A case ends automatically. Sec does not require break at the end of each case and does
not continue into the next case implicitly.
switch value {
case 1:
One()
case 2:
Two()
}
When value is 1, only One() runs.
Explicit fallthrough
fallthrough transfers control directly to the body of the next case. The next case's
condition is not tested.
switch value {
case 1:
One()
fallthrough
case 2:
OneOrTwo()
default:
Other()
}
When value is 1:
One()executes.- Control enters the next case body.
- The
case 2condition is not evaluated. OneOrTwo()executes.
fallthrough must be the final non-empty statement in a case. It is not allowed in the
final case or in default. It is only valid directly inside a switch case body.
case 1:
fallthrough
MoreWork() // Error: fallthrough must be the final statement.
Empty Cases
An empty case is legal and does not fall through implicitly.
switch value {
case 1:
case 2:
Two()
}
When value is 1, no statements execute and control continues after the switch.
Use explicit fallthrough when entering the next case body is intended.
Case Scope
Each case body creates an independent lexical scope.
switch value {
case 1:
let message: string := "one"
Print(message)
case 2:
let message: string := "two"
Print(message)
}
The two variables named message are legal because the sibling case scopes do not overlap.
A variable declared in one case is not visible in another case or after the switch.
switch value {
case 1:
let local: int := 10
}
Print(local) // Error: local is outside its scope.
Variables declared before the switch are visible in all cases, and normal mutability rules apply.
let mut result: int := 0
switch value {
case 1:
result = 10
case 2:
result = 20
default:
result = 30
}
Switch Termination
A switch is considered terminating when:
- it has a
defaultclause, - every reachable case body terminates,
- every fallthrough chain eventually reaches a terminating case,
- and no reachable path continues after the switch.
fn Classify(value: int) int {
switch value {
case < 0:
return -1
case 0:
return 0
default:
return 1
}
}
This function satisfies the return requirement because every possible switch path returns.
fn Invalid(value: int) int {
switch value {
case 0:
return 0
}
} // Error: value may not match any case.
Definite Assignment
Assignment analysis merges only the paths that can continue after the switch.
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 possible continuing path assigns result before it is used.
fn Invalid(value: int) int {
let mut result: int
switch value {
case 1:
result = 10
}
return result
} // Error: value may not match case 1.
Duplicate and Overlapping Cases
Compile-time duplicate case values are invalid.
switch value {
case 1:
A()
case 1:
B()
} // Error: duplicate switch case value 1.
Compile-time ranges must not overlap.
switch score {
case 0..50:
A()
case 40..100:
B()
} // Error: switch case range overlaps previous case.
A value may not be covered by an earlier compile-time range.
switch score {
case 0..100:
InRange()
case 50:
Exact()
} // Error: switch case value 50 is already covered by previous case.
Clearly unreachable relational cases are also rejected.
switch value {
case >= 0:
NonNegative()
case > 10:
Large()
} // Error: previous case already covers this condition.
When case expressions or range bounds are dynamic, overlap may not be provable at compile time. Such cases are legal, and the first runtime match wins.
Enums and Strings
Enum values may be used as switch subjects.
switch direction {
case Direction.north:
North()
case Direction.south:
South()
default:
Other()
}
An enum switch is not required to be exhaustive. Use match when every enum value must be
handled explicitly.
Strings may be used when normal string equality is available:
switch command {
case "start":
Start()
case "stop":
Stop()
default:
Unknown()
}
A boolean subject is legal, although if/else is often clearer:
switch ready {
case true:
Start()
case false:
Wait()
}
Semantic Types and Units
Named types retain their normal identity inside switch statements.
type Speed int
let speed: Speed := 50
let minimumSpeed: Speed := 10
let maximumSpeed: Speed := 100
switch speed {
case minimumSpeed..maximumSpeed:
Normal()
}
Plain values of an unrelated type are not accepted implicitly:
switch speed {
case 0..100:
Normal()
} // Error when int is not implicitly compatible with Speed.
Unit values also retain unit compatibility rules.
switch distance {
case 0<m>..<100<m>:
Near()
case 100<m>..:
Far()
}
Values with incompatible units cannot be compared in the same switch.
switch versus match
switch performs ordered value-based branching. It does not perform structural matching.
Use switch for:
- values,
- ranges,
- relational comparisons,
- ordered boolean conditions.
Use match for:
- union variants,
Result,Option,- destructuring,
- type patterns,
- exhaustive structural branching.
match result {
Ok(value) => Use(value)
Err(error) => Handle(error)
}
The following is not switch syntax:
switch result {
case Ok(value):
}
switch is initially a statement, not an expression. Use
match for exhaustive value-producing branching or assign explicitly inside a switch.
Common Errors
Incompatible case type
let value: int := 10
switch value {
case "10":
Handle()
}
string is not compatible with an int subject.
Non-boolean subjectless case
switch {
case 10:
Handle()
}
Every subjectless case expression must be bool.
Multiple defaults
switch value {
default:
A()
default:
B()
}
A switch may contain only one default.
Implicit fallthrough assumption
switch value {
case 1:
case 2:
Handle()
}
Case 1 is empty. It does not continue into case 2. Add
fallthrough explicitly when that is intended.
Using continue for cases
continue belongs to loops. It does not advance to the next switch case. Use
fallthrough to enter the next case body.
Summary of Rules
- A switch may have a subject expression or no subject.
- The subject is evaluated exactly once.
- Cases are tested from top to bottom.
- The first matching case wins.
- Comma-separated case items mean logical OR.
- Value, range and relational cases must be compatible with the subject type.
- Every case in a subjectless switch must be
bool. defaultis optional, unique and always final.- Cases do not fall through implicitly.
fallthroughenters only the next case body and must be the final statement.- Each case body has its own lexical scope.
- Duplicate and provably overlapping compile-time cases are errors.
switchis value-based and non-structural.matchowns variants, destructuring and exhaustive structural branching.switchis a statement, not an expression.