LEVIATHAN v962456e · 962456eee1

Expressions

match — type, value and range dispatch

Select the first arm whose pattern matches a value, a type or a range, either as an expression that yields a value or as a statement.

since 0.1.0-alpha.1linuxwindowswasm

Description

match (subject) {
    pattern => body;
    pattern => { block }
    else => body;
}

A match compares subject with each arm in source order and runs the first arm that matches; later arms are not examined. A pattern is one of:

  • a type (Dog =>): matches when the subject is an instance of that type, and narrows the subject to that type inside the arm;
  • a value (0 =>, Color::Red =>): matches when the subject equals the value;
  • a range (1..9 =>): matches when the subject lies between the two endpoints, both endpoints included;
  • else: matches anything that reached it.

An arm body is either an expression followed by ; or a block in braces. match is an expression when its value is used (the arms then yield the value) and a statement when it stands alone. A match statement needs no trailing ; after its closing brace.

match is a reserved word.

Type arms, value arms and range arms

class Animal { }
class Dog : Animal { }
class Cat : Animal { }

string describe(Animal a) {
    return match (a) {
        Dog => "a dog";
        Cat => "a cat";
        else => "some other animal";
    };
}

string size(int n) {
    return match (n) {
        0 => "zero";
        1..9 => "small";
        10..99 => "medium";
        else => "large";
    };
}

console.writeln(describe(Dog()));
console.writeln(describe(Animal()));
console.writeln(size(0));
console.writeln(size(9));
console.writeln(size(10));
console.writeln(size(-4));
a dog
some other animal
zero
small
medium
large

Rules

  • Arms are tried in source order and the first match wins. When two arms overlap, the earlier one decides: with 90..100 before 0..100, the value 95 takes the first arm.
  • A match must be exhaustive. It is complete when it ends in an else arm, when its subject has a closed union type and every member of the union has an arm, or when its subject is an enum and every member of the enum has an arm. Otherwise the compiler reports match must be exhaustive: add an 'else' arm. An open class hierarchy always needs an else arm.
  • A type arm narrows the subject: inside the arm the subject has the arm's type, so its members can be used directly.
  • A range arm lo..hi => matches when subject >= lo && subject <= hi. Both endpoints are included, and an arm whose first endpoint is larger than its second (5.0..0.0) matches nothing.
  • Range arms work for every subject that the comparison operators order: all the integer widths, the float types, string (compared lexicographically), and a char compared against integer endpoints. The endpoints and the subject need not share a type: an int endpoint against a float subject promotes like >= does, so match (2.5) { 0..5 => ... } matches.
  • Range arms use the ordering of the comparison operators, so a NaN subject matches no range arm and falls to else, -0.0 counts as equal to 0.0, and an infinity lies outside every finite range.
  • A name written with :: in an arm is resolved by what it names: a type qualified by its namespace is a type pattern, while an enum member or a namespace constant is a value pattern.

Examples

A match over a union, narrowing in each arm

string show(int | string v) {
    return match (v) {
        int => "int ${v + 1}";
        string => "string of length ${v.length()}";
    };
}

console.writeln(show(41));
console.writeln(show("hello"));
int 42
string of length 5

Range arms on floats, strings and chars

string temperature(float t) {
    return match (t) {
        0.0..15.0 => "cold";
        15.0..30.0 => "mild";
        else => "out of range";
    };
}

string half(string s) {
    return match (s) {
        'a'..'m' => "first half";
        else => "second half";
    };
}

string kind(char c) {
    return match (c) {
        48..57 => "digit";
        97..122 => "lowercase letter";
        else => "other";
    };
}

console.writeln(temperature(2.5));
console.writeln(temperature(15.0));
console.writeln(temperature(99.0));
console.writeln(half("cat"));
console.writeln(half("zebra"));
char q = 'q';
console.writeln(kind(q));
char seven = '7';
console.writeln(kind(seven));
cold
cold
out of range
first half
second half
lowercase letter
digit

Enum members, namespace constants and the statement form

enum Color { Red, Green, Blue }

namespace Limits { const int Max = 3; }

string name(Color c) {
    return match (c) {
        Color::Red => "red";
        Color::Green => "green";
        Color::Blue => "blue";
    };
}

string check(int n) {
    return match (n) {
        Limits::Max => "at the limit";
        else => "below or above";
    };
}

console.writeln(name(Color::Green));
console.writeln(check(3));

match (3) {
    1 => console.writeln("one");
    3 => { console.writeln("three"); }
    else => console.writeln("other");
}
green
at the limit
three
int n = 7;
match (n) {
    1 => console.writeln("one");
    2 => console.writeln("two");
}
// error: match must be exhaustive: add an 'else' arm
not run — shows a compile error

Notes

  • A char subject compared against char endpoints, as in 'a'..'z', does not match anything. Write the endpoints as integer code points (97..122) instead, as the kind example above does. A string subject with string endpoints is unaffected.
  • A range arm's endpoints are not checked against the subject's type. An arm whose subject has no ordering, such as an array or a bool endpoint, compiles but behaves differently from one engine to another, so keep range arms to the subjects listed in the rules.

See also

  • Exception — The base class of the standard exceptions, holding a message.
  • Exceptions: throw, try and catch — Throwing a value that implements IException, catching it by type, and the standard exception hierarchy.