Pattern Matching
Patterns occur only in explicit matching positions such as switch cases. They test shape and bind local names; they are not arbitrary Boolean expressions.
Pattern forms
Initial patterns include variants, typed bindings, wildcard _, literals, and null. Each data position of a variant accepts another complete pattern, so patterns may nest recursively.
enum Tree<T> {
Leaf(T value),
Branch(Tree<T> left, Tree<T> right)
}
case Branch(Leaf(Integer value), _) {
printLine(value)
}The variant name must belong to the enum at that position. Argument order must match the variant declaration. Trailing data positions with defaults may be omitted, equivalent to _. Integer value is a typed binding: on a match, it binds the matched value as the declared type under the name value, visible only within that case block.
_ matches anything and binds no name. A literal matches under the type's built-in language equality and must be compatible with the position's static type. null matches only a null value in a nullable position.
case Leaf(0) { printLine("zero") }
case Leaf(null) { printLine("missing nullable value") }
case _ { printLine("other") }A typed binding may use the matched value's static type or a nominal subtype. A subtype binding checks the dynamic type and binds the narrowed value. Member shapes are not matched.
A nullable binding matches both null and non-null values of its type: String? text may bind null, while String text matches only non-null strings. A nullable subtype binding does not cover other non-null subtypes; exhaustiveness and unreachable-branch checks use the same rules.
Generic type patterns retain all type arguments: Box<Integer> does not match Box<String>, while Box<T> uses the current invocation's reified type argument. The following executable example also combines enum patterns, switch results, and if results:
enum Delivery {
Pending,
Sent(String trackingCode),
Failed(String reason)
}
class Box<T> {
T item
}
String describe(Delivery delivery) {
switch delivery {
case Pending { break "waiting" }
case Sent(String code) { break "tracking " + code }
case Failed(String reason) { break "failed: " + reason }
}
}
Boolean matches<T>(Any candidate) {
switch candidate {
case Box<T> box { break true }
case _ { break false }
}
}
String nextAction(Delivery delivery, Boolean notify) {
String action = if notify { "notify" } else { "record" }
String state = switch delivery {
case Pending { break "pending" }
case Sent(_) { break "sent" }
case Failed(_) { break "failed" }
}
action + " " + state
}
main() {
Delivery sent = Delivery.Sent(trackingCode: "N-42")
Delivery failed = Delivery.Failed(reason: "address")
printLine(describe(sent))
printLine(describe(failed))
printLine(nextAction(delivery: Delivery.Pending, notify: true))
printLine(nextAction(delivery: sent, notify: false))
Any number = Box<Integer>(item: 7)
printLine(matches<Integer>(number))
printLine(matches<String>(number))
}Matching process
One pattern is checked outside-in and left-to-right at each level. A failure leaves no local binding or other observable state. Cases select the first matching pattern in source order. A case completely covered by earlier patterns is unreachable and causes a compile error. Patterns inspect only enum variants, nominal types, and values; they invoke no user-defined matching protocol.