Fundamental patterns
Fundamental patterns check a value's type or contents. A successful pattern can also give the matched value a name for use in that branch.
Type and binding patterns
Type— type pattern. Succeeds when the scrutinee can be treated asType. If the pattern introduces no designation, it behaves like a type test.Type name— typed binding. Succeeds when the scrutinee can be treated asType, then binds the converted value tonameas an immutable local in the success scope.When
Typeis an open generic type name written without explicit type arguments (for exampleBoxwhereBox<T>exists), Raven infers type arguments from the scrutinee when possible.This inference applies uniformly in:
ispatterns (if value is Box box { ... })matchexpression arms (match value { Box box => ... })matchstatement arms (match value { Box box => ... })
Example:
class Box<T> {} let value: Box<int> = Box<int>() if value is Box box { // box : Box<int> }let name/var name/val name— variable pattern. Always matches and introduces a binding.let/valproduce an immutable local;varproduces a mutable one.Parenthesized designations such as
let (first, second): (int, string)bind each element positionally.Explicit binding keyword required. In pattern position, introducing a new binding always requires an explicit binding keyword (
let,var, orval). A bare identifier never introduces a binding. It may name a type or a compile-time constant, but runtime values require an explicit== valuecomparison pattern.For example:
Ok(42)matches the literal value42.Ok(== discountedProduct)matches the runtime value of the in-scope symboldiscountedProduct.Ok(let n)binds the payload to a new immutable localn.
The same rule applies uniformly in positional patterns and discriminated-union case payloads.
Discards
_/Type _— discard. Matches without introducing a binding. The typed form asserts the value can be treated asTypewhile still discarding it. Because_is reserved for discards, writing_never creates a binding.Discards participate in exhaustiveness: an unguarded
_arm is a catch-all and satisfies any remaining cases (even if earlier arms introduced bindings).
Constant patterns
literal— constant pattern. Matches when the scrutinee equals the literal value (true,"on",42, ornull).identifier— type or compile-time constant pattern. A bare identifier may name a type or a compile-time constant. A local, parameter, property, or non-constant field is rejected here; write== identifierto compare with its runtime value, or uselet/var/valto introduce a binding.Type.Member— qualified constant/value pattern. When the qualified name resolves to a static constant-like value, such asMath.PIor an enum member such asJsonValueKind.True, the pattern matches when the scrutinee equals that value. Qualified names that resolve to types remain type/declaration patterns..Member— target-typed value pattern. When the scrutinee type is known (for example, an enum type or a type with static fields), the leading-dot expression resolves against that target type and matches the resulting value. Equality expressions also provide a target type from the opposite operand, sovalue == .Memberis equivalent tovalue == EnumType.Memberwhenvaluehas the enum/member-bearing type.
A bare identifier in pattern position is context-sensitive only between a type and a compile-time constant. Runtime value comparisons are always explicit.
Comparison patterns
< expr,<= expr,> expr,>= expr,== expr,!= expr— comparison pattern. Matches when the scrutinee compares to the operand using the given operator.The operand must be a side-effect-free expression (for example, literals, constants, or other stable values), ensuring comparison patterns remain predictable and optimizable.
Explicit runtime-value comparisons and guarded bindings work uniformly in nested patterns. For example, all of these case payloads use the general pattern rules:
Ok(== discountedProduct),Ok(let productName when == discountedProduct),Ok(let productName when productName == discountedProduct), andOk(let productName: string when productName == discountedProduct).The operand type must match the scrutinee type after nullable/plain-type unwrapping. Ordinary implicit numeric widening is not applied inside comparison patterns, so matching an
intscrutinee with> 0.5is an error.Comparison patterns are commonly used under
not,and, and property patterns, e.g.{ Age: not > 30 }.
Range patterns
A range pattern matches values that fall within a lower and/or upper bound
using .. (inclusive upper bound) or ..< (exclusive upper bound). Range
patterns are valid when the scrutinee type is orderable (for example
numeric types, char, or other types that support relational comparison).
Like comparison patterns, range bounds must match the scrutinee type after nullable/plain-type unwrapping; normal implicit numeric conversions are not applied to range bounds.
Both bounds are optional:
lo..hi— matches values greater than or equal toloand less than or equal tohi.lo..<hi— matches values greater than or equal toloand less thanhi...hi— matches values less than or equal tohi...<hi— matches values less thanhi.lo..— matches values greater than or equal tolo.
Bounds are written as expressions, but Raven may require constant-like values in contexts where the range must be known during compilation.
let value = 42
let result = match value {
..4 => "No"
40..43 => "Yes"
_ => "Other"
}
Range patterns participate in exhaustiveness and subsumption analysis alongside comparison patterns. They are treated as syntactic sugar for a conjunction of comparison operators (for example 40..43 behaves like >= 40 and <= 43).
🧭 Disambiguation: In pattern position,
..introduces a range pattern rather than a range expression. The expression parser stops at..so the bounds are parsed independently.
Positional patterns
(element1, element2, …)— positional pattern. Matches when the scrutinee can be deconstructed positionally with the same arity (either as a tuple or via a compatibleDeconstructmethod).Positional patterns destructure by position. Each element is itself a pattern and may introduce bindings. For example:
If the scrutinee type exposes a
Deconstructmethod with matching arity (including as an extension method), the positional pattern uses that method to obtain positional values.✅
(let a, var b)(explicit mutability)✅
(a: int, b: string)(inline type annotations)✅
(int a, string b)(type-pattern + capture)✅
(a: int, _)✅
(let a, == existingValue)(explicit capture + value pattern)✅
(== existingA, == existingValue)(existing-value comparisons)
In freestanding and inline positional patterns, captured variables must use an explicit binding keyword (
let,var, orval). Comparing against an existing runtime value uses== existingValue. In assignment and declaration deconstruction (let (a, b) = expr,(a, b) = expr), bare identifiers continue to act as deconstruction targets. To constrain by type and capture a value in an element, both forms are valid:let name: Type(Raven-native typed binding style) andType name(type-pattern style). This is equivalent to introducing a binding and adding awhenguard that compares the bound value, but== exprkeeps the constraint local to the pattern and avoids an additional arm condition.An element may optionally include a name before the colon (
name: pattern) to bind the element value while still applying a nested pattern. When the positional pattern is backed byDeconstructrather than a tuple, named elements bind byDeconstructparameter name and may appear in any order. Unnamed elements continue to consume the remaining unmatched parameters in declaration order. If a supplied name does not match any supported deconstruction member/parameter, the pattern is invalid.