Definition and scope
링크A pattern describes a value to recognize and the names to bind from it. A match expression selects a case by applying a pattern and, when present, a guard.
This page owns pattern syntax, case selection, and the checking rules stated below. The execution of if let and while let, including their branches and repetition, belongs to Blocks and control flow. Collection construction, nominal declarations, and general name resolution belong to their respective Language pages.
This page defines source-order selection, exhaustive matches, and checking of unreachable cases. Each example states its expected result independently of recorded observations.
Syntax
링크Pattern = PrimaryPattern ("|" PrimaryPattern)* ;PrimaryPattern ="_" | Identifier | Literal | ConstructorPattern | ListPattern| RecordPattern | NominalRecordPattern | TypedPattern | RangePattern ;ConstructorPattern =Identifier "(" (Pattern ("," Pattern)* ","?)? ")" ;ListPattern = "[" (ListElement ("," ListElement)* ","?)? "]" ;ListElement = Pattern | ".." Pattern ;RecordPattern = "{" FieldPattern ("," FieldPattern)* ","? "}" ;FieldPattern = FieldName ":" Pattern ;NominalRecordPattern = Identifier RecordPattern ;TypedPattern = Identifier ":" Type ;RangePattern = "(" RangeEndpoint (".." | "..<") RangeEndpoint ")" ;
This is a hand-transcribed source grammar excerpt. Type includes the type parser's own alternatives; the excerpt does not settle every interaction between a type annotation and an or-pattern. Range imports the expression subset accepted at a range-pattern boundary.
The grammar defines pattern syntax. Scrutinee types, constructor arity, binding rules, and unresolved rest boundaries also affect valid programs.
MatchShape = "match" "(" Expression ")" "{" CaseShape+ "}" ;CaseShape = "case" Pattern ("if" "(" Expression ")")? "=>" CaseBody ;CaseBody = Expression ;
The + requires at least one case. This shape excerpt omits case separators and expression/block disambiguation. A case body is an expression, which may be a block expression. Write a return statement inside that block. Its transfer and cleanup follow Blocks and control flow.
Pattern forms
링크The table is a syntax index for the grammar excerpt. Its spellings are fragments, not separately executed programs. Constructor argument lists identify constructor patterns. A bare name attempts to bind, subject to the constructor-name collision rule.
| Pattern form | Representative spelling |
|---|---|
| Binding | name |
| Wildcard | _ |
| Literal | 0, true, "ready", () |
| Constructor | Some(item), None(), Empty(), Tag(payload) |
| List | [first, second], [head, . |
| Rest within a list | ., . |
| Structural record | { x: x, y: other } |
| Nominal record | Point { x: x, y: y } |
| Or-pattern | 1 \| 2 |
| Integer range | (0 ., (0 . |
| Typed binding | item: int |
Pattern forms and name interpretation
The pattern forms are binding, wildcard, literal, constructor, list, record, nominal-record, or, range, typed. Rest patterns appear in list patterns. A record pattern has at least 1 field. Each field uses field: pattern. A field without a colon and pattern is rejected with TPZ2001 during parsing.
Every constructor pattern has an argument list. A constructor with no payload uses Name(), including None(). List-element bindings have the array element type, and a rest binding has an array type.
이유
A shorthand field and an explicit mapping to the same name both bind that field value under that name. Explicit field mappings preserve this intent and use the same syntax for renaming and nested patterns. Pattern bindings preserve each field type.
Use Name(...) to test a constructor and a fresh name or _ to capture or ignore the whole value. A bare constructor name is rejected instead of becoming a catch-all.
Constructor syntax and binding names
Write Name(patterns) in every pattern position. The payload list has the declared arity. A zero-payload constructor uses Name(), including None() and user enum variants such as Empty(). Nesting does not change this syntax. Parenthesized patterns with the wrong payload count retain the existing constructor-arity diagnostic.
Write Name(arguments) in every value position at every arity. Constructor calls use None(), Empty(), or Some(value). Qualified enum construction uses the same argument list, as in Choice.. The grammar rejects a bare prelude None value. User enum constructor names are resolved and checked after parsing.
A bare identifier in a pattern attempts to bind a new name. Its capitalization never selects a constructor role. A typed binding uses the same name rule.
The constructor namespace includes prelude constructors, visible declarations and imports, and variants obtained from the known expected type after resolving aliases. A pattern binder that has the same identifier as one of those constructors is rejected with TPZ5712. This check applies at every nested pattern, to typed binders, and to constructors of another visible type. A value binding does not hide a constructor for this check. Choose a fresh name to capture the value or use _ to ignore it.
None is already a reserved binding name. Without the constructor argument list it is rejected during parsing with TPZ2012. Other bare constructor names reach the namespace check. A removed bare enum pattern is never widened into a catch-all.
이유
Bare and parenthesized nullary constructors denote the same variant without a payload. Parentheses preserve that construction or matching intent and identify constructors at every arity. A bare pattern name attempts to bind. Rejecting constructor-name collisions prevents a former tag test from silently becoming a catch-all. Capitalization, type annotations, nesting, and value shadowing do not change this rule.
Selecting a case
링크Case selection and guards
A match has at least one case and evaluates its scrutinee once. It tries cases in source order and tests the pattern of each case in turn. After a pattern succeeds it installs the bindings and evaluates the guard once if present. The guard has type bool. Bindings are available in the guard and body.
An absent guard counts as true. A false guard continues with the next case, including a case with the same pattern. The first case whose pattern and guard succeed is selected. A catch-all serves as a fallback at its written position. The selected body produces the match value. Other bodies are not evaluated.
Every match must satisfy the exhaustiveness rule during checking.
An arm body is an expression. Write a return statement inside a block, as in case true => { return value }. A return without that block is rejected during parsing with TPZ2001.
Every match follows the exhaustiveness rule.
이유
Evaluating the scrutinee once avoids repeating its effects. Source order makes the exceptional case explicit before a more general case handles the remaining inputs. A failed guard leaves later cases available. Rejecting cases that earlier unguarded patterns make unreachable prevents an arm from being silently hidden. A bare return in an arm and a block containing that return request the same exit from the enclosing function with the same value. The block expresses that transfer through ordinary statement rules while each arm has an expression body.
Wildcard pattern
_ matches any scrutinee. It is irrefutable and introduces no bindings.
이유
Ignoring a value should not introduce a name that could hide another binding. The wildcard makes that intent explicit.
The selection example records scrutinee evaluation, guard visits, and body visits. The expected trace makes skipped guards and unselected bodies observable.
Bindings and scalar patterns
링크Names in or-pattern alternatives
Each alternative of an or-pattern must bind the same set of names. Different name sets are rejected during checking with TPZ5710. The TPZ5711 examples reject a shared name with int and string payloads and accept matching int payloads. The full binding-type compatibility algorithm across alternatives remains Pending.
이유
Every successful alternative must provide the names used by the shared guard and body. Equal binding sets prevent a name from being absent on one path.
Typed binding patterns
For a known scrutinee type that overlaps the annotation, a typed binding has the overlapping part of those two types. The annotation selects the matched type.
이유
The overlap identifies values that can satisfy both the scrutinee type and the annotation. It gives the binding a type valid on a successful match.
Integer range patterns
An integer range pattern compares an int scrutinee with int endpoints. The value must be greater than or equal to the lower endpoint and satisfy the upper comparison. Both comparisons must succeed. The pattern introduces no bindings.
| Pattern operator | Upper comparison |
|---|---|
. | Less than or equal to the upper endpoint |
. | Less than the upper endpoint |
이유
Using the same endpoint operators as range expressions keeps boundary meaning consistent between matching and iteration.
A single list rest and its surrounding elements
A pattern after the rest marker is required. Write . to bind the rest or . to ignore it. An omitted pattern is rejected during parsing with TPZ2001.
A list pattern with one rest may put it before, between, or after fixed elements. The input array must contain at least as many elements as the fixed prefix and suffix together. Those elements are matched in order. The rest has type Array<T> and may be empty. Admission of multiple rest elements remains Pending. Their implementation limitation is documented separately.
이유
An omitted rest pattern and an explicit wildcard both discard the unmatched slice. Writing the wildcard preserves that intent and distinguishes it from binding the slice. One rest determines the slice between the fixed prefix and suffix.
Range pattern boundaries
Write a range pattern as (lower . or (lower .. The outer parentheses belong to the range pattern and close before the case arrow. Endpoints use the ordinary additive-expression grammar and must have type int. An arm may produce a lambda without taking the case arrow into an endpoint. An unwrapped range pattern is rejected during parsing with TPZ2001.
이유
A bare range pattern and the same range inside parentheses test the same endpoints with the same inclusive or exclusive upper bound. The parentheses preserve that interval intent and give the pattern a closing boundary before the case arrow. This separates endpoint expressions from an arm that produces a lambda without changing expression parsing according to the endpoint spelling.
Accepted matches and coverage
링크Coverage of a known bool
The bool examples apply the exhaustiveness rule to true, false. The missing-case example reports TPZ5021.
이유
These examples show the closed-type coverage required by the general exhaustiveness rule.
The exhaustiveness rule applies to closed and open types, including nested payloads.
Cases are tried in source order. A later case may overlap an earlier case when it still matches some reachable input. An unguarded wildcard or total binding selects at its written position. Earlier unguarded cases that cover every value a later pattern can match make that later case a checking error. Every arm is still type-checked.
Reachable overlapping cases
Cases are tried in source order. A case may overlap an earlier case if it can match an input that reaches it. A specific case can therefore precede a general case. An unguarded wildcard or total binding is an ordinary case and acts as a fallback at its written position. There is no separate residual fallback selection.
A later case is unreachable when the union of preceding unguarded patterns covers every value its pattern could match. This includes a case shadowed by a single catch-all or by several earlier cases. Pattern typing runs before this comparison. A guard cannot establish unconditional coverage because it may fail. Overlapping cases with reachable inputs remain valid even when their intersection is unknown.
Alternatives of one or-pattern describe one case domain. Their overlap does not make a later case unreachable. Binding consistency is checked by its separate rule. All case bodies are checked even when their patterns are unreachable.
An unreachable case is rejected during checking with TPZ5713. A typed pattern incompatible with the known scrutinee type still uses TPZ5001. Match completeness follows the exhaustiveness rule.
이유
An exceptional case can intentionally precede a general case. That use of overlap has a different intent from dividing a domain into mutually exclusive categories. Source order expresses which matching case takes priority. A failed guard lets later cases handle the input. Rejecting a case hidden by earlier unguarded patterns prevents code order from silently disabling a branch.
Every match is exhaustive
Every match must be exhaustive during checking. A closed type has cases that can all be enumerated. Enums, Result, Option, bool, and closed unions must cover every case. Coverage also examines closed types inside payloads. For example, Option<bool> is covered by Some(true), Some(false), and None().
Open types include int, float, string, arrays, ranges, type parameters, and other types whose cases cannot be enumerated. A match over an open type ends with an unguarded wildcard or name pattern. A guarded arm does not contribute to coverage, even when its guard is a literal true.
Missing coverage is rejected during checking with TPZ5021. TPZ4006 is retired in 6.0.0 and is replaced by TPZ5021. This rule does not change refutable let or for patterns.
Case selection follows source order. TPZ5021 reports missing coverage. TPZ4006 records the retired runtime failure.
이유
Checking every match removes execution-time match failure. A program handles each input before it runs.
Examples and expected results
링크Each example lists its expected result and the recorded public CLI observation.
Observe the first successful case and guard
링크import std.iofunction readValue() -> int {io.writeLine("scrutinee")2}function testCase(label: string, answer: bool) -> bool {io.writeLine(label)answer}function selectedBody(label: string) -> string {io.writeLine("body: {label}")label}let answer = match (readValue()) {case 1 if (testCase("unmatched guard", true)) => selectedBody("one")case 2 if (testCase("selected guard", true)) => selectedBody("selected")case _ => selectedBody("fallback")}io.writeLine(answer)
scrutinee
selected guard
body: selected
selected
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
scrutineeselected guardbody: selectedselected
The input selects one explicit pattern. Its guard and body run once. A guard belonging to another pattern is not evaluated. This revised expectation has no current product receipt.
Literal alternatives and integer range boundaries
링크import std.iofunction classify(value: int) -> string {match (value) {case 1 | 2 => "choice"case (3..<6) => "inside"case (7..9) => "closed"case _ => "outside"}}io.writeLine(classify(1))io.writeLine(classify(2))io.writeLine(classify(3))io.writeLine(classify(5))io.writeLine(classify(6))io.writeLine(classify(9))io.writeLine(classify(10))
choice
choice
inside
inside
outside
closed
outside
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
choicechoiceinsideinsideoutsideclosedoutside
관측한 단언 · 통과 · 5.21.3
종료 코드 0.
choicechoiceinsideinsideoutsideclosedoutside
표준 오류 출력 없음.
- 통과 · expected 0; observed 0
- 통과 · stdout matches the authored expectation
- 통과 · stderr matches the authored expectation
The expected values exercise both endpoints of the half-open interval and the upper endpoint of the closed interval. They do not establish coverage of an integer domain.
Check field, rest, typed and constructor bindings
링크record Point {x: int,y: int,}function pointTotal(value: Point) -> int {match (value) {case Point { x: 0, y: other } => othercase remaining => remaining.x + remaining.y}}function structuralTotal(value: { x: int, y: int }) -> int {match (value) {case { x: 0, y: other } => othercase remaining => remaining.x + remaining.y}}function remainder(values: Array<int>) -> Array<int> {match (values) {case [_, ..tail] => tailcase _ => []}}function integerPart(value: int | string) -> int {match (value) {case item: int => itemcase _ => 0}}function selected(value: Result<int, int>) -> int {match (value) {case Ok(item) | Err(item) => item}}
기대 결과
기대 결과
검사가 프로그램을 받아들입니다. 종료 코드 0.
This is a complete checking fixture. Explicit result types make the field, remainder and typed-binding uses visible to checking. The record declaration and collection literals provide context; their construction rules are owned by other Language pages.
A known bool with one missing case
링크function flag(value: bool) -> int {match (value) {case true => 1}}
표준 출력 없음.error[TPZ5021]: non-exhaustive match: missing `false`
--> example.tpz:2:5
|
2 | match value {
| ^^^^^^^^^^^^^
example.tpz: 1 type diagnostic
기대 결과
기대 결과
거절 단계 check. 종료 코드가 0이 아닙니다.
missing-unguarded-false-case-for-known-bool at
example.tpz:2:5
관측한 단언 · 통과 · 5.21.3
종료 코드 1.
표준 출력 없음.
error[TPZ5021]: non-exhaustive match: missing `false`--> example.tpz:2:5|2 | match value {| ^^^^^^^^^^^^^example.tpz: 1 type diagnostic
- 통과 · expected nonzero; observed 1
- 통과 · checking TPZ5021; cause missing-unguarded-false-case-for-known-bool; primary location asserted; additional diagnostics allow
The independent rejection expectation requires the match's missing bool coverage. A different parsing or name-resolution failure does not prove this claim. Additional diagnostics may be present; exact prose and diagnostic count are not fixed.
Cover both bool values
링크function flag(value: bool) -> int {match (value) {case true => 1case false => 0}}
example.tpz: types-ok (1 module)
example.tpz: resolve-ok (1 module)
기대 결과
기대 결과
검사가 프로그램을 받아들입니다. 종료 코드 0.
관측한 단언 · 통과 · 5.21.3
종료 코드 0.
example.tpz: types-ok (1 module)example.tpz: resolve-ok (1 module)
표준 오류 출력 없음.
- 통과 · expected 0; observed 0
The repaired program adds the missing false case. Its expected result is successful checking of the complete fixture.
Reject different names across alternatives
링크function selected(value: Result<int, int>) -> int {match (value) {case Ok(left) | Err(right) => 0}}
표준 출력 없음.error[TPZ5710]: or-pattern alternatives must bind the same names — `left` is bound in one alternative but not another
--> example.tpz:3:14
|
3 | case Ok(left) | Err(right) => 0
| ^^^^^^^^
error[TPZ5710]: or-pattern alternatives must bind the same names — `right` is bound in one alternative but not another
--> example.tpz:3:25
|
3 | case Ok(left) | Err(right) => 0
| ^^^^^^^^^^
example.tpz: 2 type diagnostics
기대 결과
기대 결과
거절 단계 check. 종료 코드가 0이 아닙니다.
or-pattern-alternatives-bind-different-name-sets at
example.tpz:3:14
관측한 단언 · 통과 · 5.21.3
종료 코드 1.
표준 출력 없음.
error[TPZ5710]: or-pattern alternatives must bind the same names — `left` is bound in one alternative but not another--> example.tpz:3:14|3 | case Ok(left) | Err(right) => 0| ^^^^^^^^error[TPZ5710]: or-pattern alternatives must bind the same names — `right` is bound in one alternative but not another--> example.tpz:3:25|3 | case Ok(left) | Err(right) => 0| ^^^^^^^^^^example.tpz: 2 type diagnostics
- 통과 · expected nonzero; observed 1
- 통과 · checking TPZ5710; cause or-pattern-alternatives-bind-different-name-sets; primary location asserted; additional diagnostics allow
The intended cause is the unequal binding-name sets, not constructor arity or a missing case. The checker can report both directions of the disagreement. The accompanying accepted selected function binds item in both alternatives.
An unguarded catch-all makes the following case unreachable
링크let value: int = 7let selected = match (value) {case _ => "first"case later: int => "second"}
기대 결과
The first wildcard covers every integer. The later typed binding can never be selected and is rejected during checking. The rejection expects TPZ5713 during checking with cause match-.
A later wildcard is covered by two earlier unguarded cases
링크let value: bool = truelet result = match (value) {case true => "yes"case false => "no"case _ => "hidden"}
기대 결과
The true and false cases together cover every Boolean value. Their union makes the final wildcard unreachable. The rejection expects TPZ5713 during checking with cause match-.
Every case is checked even after a catch-all
링크let value: int = 7let selected = match (value) {case _ => "first"case impossible: string => "second"}
기대 결과
The expected result follows from current SELF source. Execution against public Topaz 5.21.3 is pending CI.
One rest may precede, separate, or follow fixed elements
링크import std.iolet middle = match ([1, 2, 3, 4]) {case [first, ..rest, last] => "{first} {rest.length} {last}"case _ => "miss"}let leading = match ([1, 2, 3, 4]) {case [..rest, last] => "{rest.length} {last}"case _ => "miss"}let trailing = match ([1, 2, 3, 4]) {case [first, ..rest] => "{first} {rest.length}"case _ => "miss"}let empty: Array<int> = []let remaining = match (empty) {case [..rest] if (rest.length == 0) => rest.lengthcase remaining => remaining.length}io.writeLine(middle)io.writeLine(leading)io.writeLine(trailing)io.writeLine("{remaining}")
1 2 4
3 4
1 3
0
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
1 2 43 41 30
The expected result follows from current SELF source. Execution against public Topaz 5.21.3 is pending CI.
Multiple rest elements retain an intended emission refusal
링크let value = match ([1, 2]) {case [..left, ..right] => 0case _ => 1}
기대 결과
The existing Rust backend refuses this shape before generating output. Current SELF emission does not establish the same refusal. The rejection expectation is retained; its exact product diagnostic and location await verification.
Rejecting a structural field shorthand
링크let { value } = { value: 1 }
기대 결과
Write the field name, a colon, and its binding pattern. The missing colon is reported at the closing brace.
Rejecting a nominal field shorthand
링크record Point { x: int }let Point { x } = Point { x: 1 }
기대 결과
Write the field name, a colon, and its binding pattern. The missing colon is reported at the closing brace.
Binding and renaming fields explicitly
링크import std.iorecord Point { x: int, y: int }let { value: value } = { value: 7 }let Point { x: first, y: second } = Point { x: 2, y: 3 }io.writeLine("{value} {first} {second}")
7 2 3
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
7 2 3
The same field mapping syntax binds a field to its own name or to a different name.
Rejecting a return without an arm block
링크function choose(flag: bool) -> int {while (true) {let value = match (flag) {case true => return 7case false => 3}return value}0}
기대 결과
A return statement needs a block when used as a match arm body. The rejection points to the return keyword.
Returning through a match arm block
링크import std.iofunction choose(flag: bool) -> int {while (true) {let value = match (flag) {case true => { return 7 }case false => 3}return value}0}io.writeLine("{choose(true)}")io.writeLine("{choose(false)}")
7
3
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
73
The true arm returns from the function through the surrounding binding and loop. The false arm supplies a value to the ordinary return statement.
Rejecting a rest marker without a pattern
링크let count = match ([1, 2]) {case [..] => 2}
기대 결과
Write . to ignore the rest. The error points to the delimiter where the required pattern is missing.
Rejecting an omitted middle rest pattern
링크let total = match ([1, 2, 3]) {case [first, .., last] => first + lastcase _ => 0}
기대 결과
Write . to ignore the rest. The error points to the delimiter where the required pattern is missing.
Ignoring a rest with an explicit wildcard
링크import std.iolet middle = match ([1, 2, 3, 4]) {case [first, .._, last] => "{first} {last}"case _ => "miss"}let leading = match ([1, 2, 3, 4]) {case [.._, last] => lastcase _ => 0}let trailing = match ([1, 2, 3, 4]) {case [first, .._] => firstcase _ => 0}let empty: Array<int> = []let ignored = match (empty) {case [.._] if (empty.length == 0) => truecase _ => false}io.writeLine(middle)io.writeLine("{leading}")io.writeLine("{trailing}")io.writeLine("{ignored}")
1 4
4
1
true
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
1 441true
The wildcard ignores a leading, middle, or trailing slice. A rest wildcard also matches an empty array.
Constructor patterns keep parentheses at every arity
링크import std.ioenum Choice { Empty, Filled(int), Pair(int, int) }function read(value: Choice) -> int {match (value) {case Empty() => 0case Filled(number) => numbercase Pair(left, right) => left + right}}function nested(value: Option<Choice>) -> int {match (value) {case None() => -1case Some(Empty()) => 0case Some(Filled(number)) => numbercase Some(Pair(left, right)) => left + right}}io.writeLine("{read(Choice.Empty())}")io.writeLine("{read(Choice.Filled(7))}")io.writeLine("{read(Choice.Pair(2, 3))}")io.writeLine("{nested(None())}")io.writeLine("{nested(Some(Choice.Empty()))}")
0
7
5
-1
0
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
075-10
Value expressions and constructor patterns keep parentheses at every arity. The nested match covers every Option and Choice variant.
A fresh bare name captures an enum value
링크import std.ioenum Choice { Empty, Filled(int) }function identity(value: Choice) -> Choice {match (value) {case captured => captured}}function read(value: Choice) -> int {match (value) {case Empty() => 0case Filled(number) => number}}io.writeLine("{read(identity(Choice.Filled(8)))}")
8
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
8
The bare name captured is a binding in every expected type. SELF currently treats an unknown top-level enum-case spelling as a variant typo, so this intended acceptance is recorded under the implementation limitation.
A fresh typed name captures the whole enum
링크import std.ioenum Choice { Empty, Filled(int) }function identity(value: Choice) -> Choice {match (value) {case captured: Choice => captured}}function upper(value: int) -> int {match (value) {case UPPER => UPPER}}function read(value: Choice) -> int {match (value) {case Empty() => 0case Filled(number) => number}}io.writeLine("{read(identity(Choice.Filled(8)))}")io.writeLine("{upper(9)}")
8
9
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
89
An annotation refines the matched type. It does not turn a binding into a constructor or permit a constructor-name collision. An uppercase fresh name is still a binding.
A bare nullary variant cannot become a catch-all
링크enum Choice { Empty, Filled(int) }function read(value: Choice) -> int {match (value) {case Empty => 0case Filled(number) => number}}
기대 결과
Write Empty() to test the nullary variant. The removed spelling must be rejected instead of accepting every Choice value.
Nested constructors require the same argument list
링크enum Choice { Empty, Filled(int) }function read(value: Option<Choice>) -> int {match (value) {case Some(Empty) => 0case _ => 1}}
기대 결과
Write Some(Empty()). Nesting does not turn a constructor spelling into a binding.
A type annotation cannot bypass a constructor-name collision
링크enum Choice { Empty, Filled(int) }function read(value: Choice) -> int {match (value) {case Empty: Choice => 0}}
기대 결과
Choose a fresh name such as captured: Choice for a typed catch-all, or write Empty() to test the variant.
Constructor names stay reserved for patterns across expected types
링크enum Mode { Idle }function read(value: int) -> int {match (value) {case Idle => 0}}
기대 결과
Idle is visible in the constructor namespace. Changing the scrutinee type or introducing a value with that name does not make this spelling a pattern binding.
None needs parentheses in a constructor pattern
링크function read(value: Option<int>) -> int {match (value) {case Some(number) => numbercase None => 0}}
기대 결과
Write None() for the constructor pattern. None remains a reserved binding name. The rejection uses the existing TPZ2012 identity and reserved-None producer message.
None needs parentheses in a constructor value
링크let maybe: Option<int> = None
기대 결과
Write None() when constructing an absent Option value. Bare None is rejected during parsing with TPZ2012 and the same reserved-None cause as the bare pattern example.
Specific cases before a general fallback
링크import std.iofunction first(value: int) -> string {match (value) {case 1 => "one"case _ => "rest"}}function typed(value: int) -> string {match (value) {case 1 => "one"case captured: int => "rest"}}function last(value: int) -> string {match (value) {case 2 => "two"case 1 => "one"case _ => "rest"}}io.writeLine("{first(1)} {typed(1)} {last(1)}")io.writeLine("{first(3)} {typed(3)} {last(3)}")
one one one
rest rest rest
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
one one onerest rest rest
Specific cases are tried first. The wildcard and total typed binding handle values that reach them. Putting the general case first makes the following specific cases unreachable and causes a checking error.
A failed guard lets the same pattern match later
링크import std.iofunction check(label: string, ok: bool) -> bool {io.writeLine(label)ok}function choose(value: int) -> string {match (value) {case 2 if (check("guard for two", false)) => "guarded two"case 2 => "unguarded two"case _ => "other"}}io.writeLine(choose(2))io.writeLine(choose(3))
guard for two
unguarded two
other
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
guard for twounguarded twoother
The first case matches 2 but its guard is false. Selection continues to the next case, which uses the same pattern without a guard. The other input reaches the wildcard.
Cover type and list domains with a record fallback
링크import std.iofunction kind(value: int | string) -> string {match (value) {case number: int => "integer"case text: string => "string"}}function row(value: { x: int, y: int }) -> string {match (value) {case { x: 0, y: _ } => "zero"case { x: 1, y: _ } => "one"case _ => "other"}}function size(values: Array<int>) -> string {match (values) {case [] => "empty"case remaining => "nonempty"}}io.writeLine(kind(1))io.writeLine(kind("a"))io.writeLine(row({ x: 0, y: 9 }))io.writeLine(row({ x: 1, y: 9 }))io.writeLine(size([]))io.writeLine(size([1]))
integer
string
zero
one
empty
nonempty
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
integerstringzerooneemptynonempty
The typed cases cover the union. The list cases cover empty and nonempty arrays. The record match reaches its final case for other field values.
Adjacent half-open and closed intervals do not overlap
링크import std.iofunction band(value: int) -> string {match (value) {case (0 ..< 5) => "low"case (5 .. 9) => "high"case _ => "outside"}}io.writeLine(band(0))io.writeLine(band(4))io.writeLine(band(5))io.writeLine(band(9))io.writeLine(band(10))
low
low
high
high
outside
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
lowlowhighhighoutside
The first interval excludes 5 and the second includes it. The wildcard follows both intervals and handles values outside them.
Dynamic bounds with a later catch-all
링크import std.iofunction bound(value: int) -> int { value }function band(value: int, low: int, high: int) -> string {match (value) {case (low ..< bound(high)) => "inside"case _ => "outside"}}io.writeLine(band(2, 0, 5))io.writeLine(band(5, 0, 5))
inside
outside
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
insideoutside
The dynamic range is tried before the wildcard. Its runtime bound test selects the first case when it succeeds. Otherwise the wildcard handles the value.
A repeated unguarded literal case is unreachable
링크function choose(value: int) -> int {match (value) {case 1 => 10case 1 => 20case _ => 0}}
기대 결과
The first unguarded case already handles every value the second identical literal pattern can match. The rejection expects TPZ5713 during checking with cause match-.
A specific constructor can precede a general constructor
링크import std.iofunction choose(value: Option<int>) -> string {match (value) {case Some(0) => "zero"case Some(number) => "other {number}"case None() => "none"}}io.writeLine(choose(Some(0)))io.writeLine(choose(Some(7)))io.writeLine(choose(None()))
zero
other 7
none
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
zeroother 7none
Some(0) matches both constructor patterns. The first handles zero while the next handles other payloads.
Overlapping record patterns leave later inputs
링크import std.iofunction choose(value: { x: int, y: int }) -> string {match (value) {case { x: 0 } => "x"case { y: 0 } => "y"case _ => "other"}}io.writeLine(choose({ x: 0, y: 0 }))io.writeLine(choose({ x: 3, y: 0 }))io.writeLine(choose({ x: 3, y: 4 }))
x
y
other
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
xyother
Both field tests succeed for the first value, so the first case wins. A value with nonzero x and zero y still reaches the second case.
Overlapping list patterns follow source order
링크import std.iofunction choose(values: Array<int>) -> string {match (values) {case [1, .._] => "starts with one"case [_, 1] => "ends with one"case _ => "other"}}io.writeLine(choose([1, 1]))io.writeLine(choose([2, 1]))io.writeLine(choose([2, 2]))
starts with one
ends with one
other
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
starts with oneends with oneother
The first pattern handles arrays starting with one. The second can still handle arrays ending with one that do not start with one.
Overlapping guarded cases follow source order
링크import std.iofunction choose(value: int) -> string {match (value) {case number if (number > 0) => "positive"case number if (number <= 0) => "nonpositive"case _ => "other"}}io.writeLine(choose(4))io.writeLine(choose(0))
positive
nonpositive
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
positivenonpositive
Both binding patterns accept every integer. The first guard decides whether to continue to the next case, and neither guard establishes unconditional coverage.
Overlapping alternatives describe one match case
링크import std.iofunction choose(value: int) -> string {match (value) {case 1 | 1 => "one"case _ => "other"}}io.writeLine(choose(1))io.writeLine(choose(3))
one
other
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
oneother
Two alternatives can recognize the same input without creating different case priorities. Both alternatives select the same body.
Overlapping integer intervals keep later cases reachable
링크import std.iofunction choose(value: int) -> string {match (value) {case (0 .. 5) => "lower"case (5 ..< 9) => "upper"case _ => "outside"}}io.writeLine(choose(5))io.writeLine(choose(7))io.writeLine(choose(10))
lower
upper
outside
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
lowerupperoutside
Five belongs to both intervals and selects the first. Seven reaches the second interval. The intervals do not need a disjointness proof.
Dynamic range overlap uses ordinary source order
링크import std.iofunction bound(value: int) -> int { value }function choose(value: int, low: int, high: int) -> string {match (value) {case (low ..< bound(high)) => "range"case 100 => "hundred"case _ => "outside"}}io.writeLine(choose(100, 0, 5))io.writeLine(choose(100, 0, 105))io.writeLine(choose(200, 0, 105))
hundred
range
outside
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
hundredrangeoutside
When the dynamic range excludes 100, the literal case succeeds. When it includes 100, the earlier range succeeds. An unknown intersection does not require rejection.
Reject an unwrapped range pattern
링크let value = match (2) {case 1 .. 3 => 1case _ => 0}
표준 출력 없음.기대 결과
A delimited range precedes a lambda-valued arm
링크import std.iolet low = 1let high = 3let chosen = match (2) {case (low .. high) => (value: int) => value + 1case _ => (value: int) => value}io.writeLine("{chosen(6)}")
7
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
7
A missing enum variant is rejected
링크enum Choice { Left(int), Right(int) }function read(value: Choice) -> int {match (value) {case Left(number) => number}}
기대 결과
Both enum variants are covered
링크import std.ioenum Choice { Left(int), Right(int) }function read(value: Choice) -> int {match (value) {case Left(number) => numbercase Right(number) => number + 10}}io.writeLine("{read(Choice.Left(2))}")io.writeLine("{read(Choice.Right(3))}")
2
13
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
213
An integer match needs a final fallback
링크function read(value: int) -> int {match (value) {case 0 => 10}}
기대 결과
A final name pattern covers remaining integers
링크import std.iofunction read(value: int) -> int {match (value) {case 0 => 10case remaining => remaining}}io.writeLine("{read(0)}")io.writeLine("{read(7)}")
10
7
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
107
Guarded bool arms do not establish coverage
링크function read(value: bool) -> int {match (value) {case true if (true) => 1case false if (true) => 0}}
기대 결과
A missing nested bool payload is rejected
링크function read(value: Option<bool>) -> int {match (value) {case Some(true) => 1case None() => -1}}
기대 결과
Option bool covers both payloads and None
링크import std.iofunction read(value: Option<bool>) -> int {match (value) {case Some(true) => 1case Some(false) => 0case None() => -1}}let flags = map { "on": true, "off": false }io.writeLine("{read(flags.get("on"))}")io.writeLine("{read(flags.get("off"))}")io.writeLine("{read(flags.get("missing"))}")
1
0
-1
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
10-1
Map lookup supplies Some and None values. Exhaustiveness includes each closed bool payload inside Some.
Pending contracts
링크Related diagnostics and owners
링크Or-pattern alternatives bind different names →
See Blocks and control flow for if let, while let, and return; Expressions and operators for expression grouping and range expressions; and Functions and generics for function declarations and return inference. A pattern range is defined on this page; the expression form has its own owner.
Native emitter cannot lower this construct yet →