Match Expressions
match is a cleaner alternative to long if/elif chains. It compares a single value against a list of patterns and runs the matching arm.
Basic Match
Each arm is pattern: body:
fn main() {
var day:i32 = 3
match (day) {
1: { print("Monday\n") }
2: { print("Tuesday\n") }
3: { print("Wednesday\n") }
4: { print("Thursday\n") }
5: { print("Friday\n") }
default: { print("Weekend\n") }
}
}
The default: arm runs if no other pattern matches — think of it as the else of match.
Multi-Value Patterns
One arm can match several values with comma separation:
fn main() {
var ch:i32 = 101 // 'e'
match (ch) {
97, 101, 105, 111, 117: {
print("vowel\n")
}
default: {
print("consonant\n")
}
}
}
Range Patterns
Use .. to match a range (inclusive on both ends):
fn main() {
var score:i32 = 87
match (score) {
90..100: { print("A\n") }
80..89: { print("B\n") }
70..79: { print("C\n") }
60..69: { print("D\n") }
0..59: { print("F\n") }
default: { print("invalid\n") }
}
}
Range patterns only work with integer literals — the compiler expands them into individual case labels in the generated C code.
Match with Enums
Match pairs naturally with enums:
enum Direction {
North
South
East
West
}
fn main() {
var dir = Direction.East
match (dir) {
Direction.North: { print("heading up\n") }
Direction.South: { print("heading down\n") }
Direction.East: { print("heading right\n") }
Direction.West: { print("heading left\n") }
}
}
Enum matches are exhaustive by convention — handle every variant, or use default: for “anything else.”
Returning from Match Arms
Arms can contain any statements, including return:
fn grade:str(score:i32) {
match (score) {
90..100: { return "A" }
80..89: { return "B" }
70..79: { return "C" }
60..69: { return "D" }
default: { return "F" }
}
return "F" // unreachable, but required for type checker
}
fn main() {
print("92 -> {grade(92)}\n")
print("75 -> {grade(75)}\n")
print("40 -> {grade(40)}\n")
}
Practical Example: HTTP Status Codes
fn status_category:str(code:i32) {
match (code) {
200..299: { return "success" }
300..399: { return "redirect" }
400..499: { return "client error" }
500..599: { return "server error" }
default: { return "unknown" }
}
return "unknown"
}
fn main() {
var codes:i32[5] = {200, 301, 404, 500, 999}
for (var code in codes) {
print("{code} -> {status_category(code)}\n")
}
}
Try it — Build a match that categorizes a character code as “digit” (48-57), “uppercase” (65-90), “lowercase” (97-122), or “other” in the Playground.
Expert Corner
What match compiles to: GX’s match generates a C switch statement. Multi-value patterns become multiple case labels, and range patterns are expanded by the compiler into one case per value. For small ranges this is fine; for huge ranges (0..10000), the compiler would emit thousands of case labels — use if/elif instead.
Why no “pattern matching with destructuring”: GX’s match is an enhanced switch, not Rust/Haskell-style algebraic data type matching. You can’t pattern-match a struct’s fields or bind variables inside patterns. The goal is readable control flow over integer-like values, not a functional-programming feature. Unions exist for tagged data but are matched manually with if.
Match vs if/elif: Use match when you’re comparing one value against many constants. Use if/elif when conditions involve comparisons between different variables, computed expressions, or complex boolean logic. Match is optimized by the C compiler into jump tables for dense integer ranges, which is usually faster than chained if.
Fallthrough is NOT supported: Unlike C switch, GX match arms don’t fall through. Each arm is self-contained — when it’s done, execution continues after the match. This eliminates the classic C bug of forgetting break between cases.
The exhaustiveness question: GX doesn’t enforce exhaustive matching the way Rust does with enums. If you match an enum but only handle half the variants, there’s no warning. For critical code, add default: { panic("unhandled variant") } to catch missing cases at runtime.