match and switch
When a value has several possible cases, reach for match or switch instead of a chain of ifs. match compares a value against patterns and checks that you have covered every case; switch is a statement in the style of C, with ranges, comparisons and fallthrough.
match
match is an expression: it compares a value against the cases and gives back the result of the branch that matched. Each branch is a pattern, a colon, the result and end. Several cases can share one branch by separating them with commas, and else catches everything left over.
func grade(score: int): str:
ret match score / 10:
10, 9: "excellent" end
8: "very good" end
7: "good" end
else: "needs more work" end
end
end
func main:
println grade(95), grade(80), grade(45)
endWhen match gives back a value, every branch has to produce one, all of the same type.
match as a statement
match can also stand on its own as a statement. A branch may then hold several lines, closed by its end. The subject does not have to be a number: strings work too, and are compared by their contents. The example plays back a list of commands, the way a small command-line tool or chat bot would.
func main:
commands := ["start", "status", "stop", "jump", "quit"]
mut running := false
each cmd in commands:
match cmd:
"start", "run":
running = true
println "starting"
end
"stop":
running = false
println "stopping"
end
"status":
println running ? "running" : "stopped"
end
"quit": println "bye" end
else: println "unknown command:", cmd end
end
end
endUnlike a switch, a match branch never falls into the next one; exactly one branch runs. The patterns for a number or a string are exact values. Ranges such as 1 to 9 and comparisons such as < 0 are not match patterns: for those use switch, shown below.
Matching an enum
Over an enum, the patterns are the names of its cases. The bare name Spring is enough, and the full Season.Spring works too. match checks that no case has been left out:
enum Season:
Spring, Summer, Autumn, Winter
end
func weather(s: Season): str:
ret match s:
Spring, Autumn: "mild" end
Summer: "hot" end
Winter: "cold" end
end
end
func main:
println "spring weather:", weather(Season.Spring)
println "winter weather:", weather(Season.Winter)
endDelete the Winter branch and the program no longer compiles, and the error names the missing case. So when you add a case to an enum later, the compiler shows you every match that has to learn about it. An else branch turns that check off, so leave it out when you can list every case.
Pulling out data, and guards
When enum cases carry data, a pattern can take that data apart: Cash(a) binds the amount to a, and _ skips a value you do not need. Card c binds the whole case, so its fields are reached as c.amount. A branch can also add a guard: if and a condition after the pattern. The guard can use the names the pattern bound, and if it is false, matching goes on to the next branch.
enum Payment:
Cash(amount: int)
Card(amount: int, last4: str)
Voucher(code: str)
Free
end
func receipt(p: Payment): str:
ret match p:
Cash(a) if a >= 1000: "cash " + a + ", count it twice" end
Cash(a): "cash " + a end
Card(a, last4): "card ending " + last4 + ", " + a end
Voucher(_): "paid with a voucher" end
Free: "nothing to pay" end
end
end
func main:
println receipt(Payment.Cash(1500))
println receipt(Payment.Cash(20))
println receipt(Payment.Card(80, "4421"))
println receipt(Payment.Voucher("SPRING"))
println receipt(Payment.Free)
p := Payment.Card(35, "0042")
label := match p:
Card c: "card payment of " + c.amount end
else: "another kind of payment" end
end
println label
endA guarded branch does not count as covering its case, which is why Cash(a) appears a second time without a guard. Enums with data are introduced in the enums lesson.
Struct patterns
match can test a struct against its fields. x = 0 means the field has to equal zero, and a bare name such as y binds the field's value to a variable of that name. Because patterns carry conditions, a final else is required.
struct Point:
pub x: int = 0
pub y: int = 0
end
func where(p: Point): str:
ret match p:
Point { x = 0, y = 0 }: "the origin" end
Point { x = 0, y }: "on the vertical axis, y = " + y end
Point { x, y = 0 }: "on the horizontal axis, x = " + x end
else: "somewhere else" end
end
end
func main:
println where(Point {})
println where(Point { y = 5 })
println where(Point { x = 3 })
println where(Point { x = 1, y = 2 })
endswitch
switch is a statement for choosing between cases. A case label can be one value, several values separated by commas, or a range such as 1 to 3. If a case does not end with break, execution falls into the next one, so write break wherever you do not want that.
func main:
repeat 5 in day:
switch day:
0:
println day, "Monday, the week begins"
break
end
1 to 3:
println day, "the middle of the week"
break
end
else:
println day, "the weekend is close"
end
end
end
endA switch label may begin with a comparison operator, such as >= 90. switch true takes the place of a chain of ifs: every label is a condition, and the first one that holds runs.
func main:
repeat 3 in i:
temperature := i * 15
switch true:
temperature < 10:
println temperature, "cold"
break
end
temperature < 25:
println temperature, "mild"
break
end
else:
println temperature, "hot"
end
end
end
score := 75
switch score:
>= 90:
println "excellent"
break
end
>= 60:
println "pass"
break
end
else:
println "fail"
end
end
endFalling through on purpose
Fallthrough is usually something to guard against, but it is handy when each case adds to the ones below it. In this subscription plan, Premium has everything Standard has and more, and Standard has everything Basic has. Ordering the cases from the richest down and leaving out break lists each plan's features with no repetition.
enum Plan:
Basic, Standard, Premium
end
func features(p: Plan):
println "plan", p.name(), "includes:"
switch p:
Premium:
println " offline downloads"
end
Standard:
println " HD video"
end
Basic:
println " the full catalogue"
end
end
end
func main:
features(Plan.Premium)
features(Plan.Basic)
endA switch on an enum must cover every case or have an else; otherwise it is error E103, just as with match. A switch on anything else that finds no matching label simply does nothing.
switch inside a loop
Inside a switch, break leaves the switch only, not the loop around it. To leave the loop as well, write break 2. continue is not caught by the switch: it goes straight to the next turn of the loop. The example averages sensor readings, skips the value that means "offline" and stops at the end marker.
func main:
readings := [21, -1, 23, 999, 25, 0, 30]
mut total := 0
mut count := 0
each r in readings:
switch r:
-1:
println "sensor offline, skipped"
continue
end
999:
println "end-of-data marker, stopping"
break 2
end
else:
total += r
count += 1
end
end
println "reading", r, "accepted"
end
println "average of", count, "readings:", total / count
endmatch or switch?
| Situation | Use |
|---|---|
| You need a value back | match |
| Enum cases carry data, or you need guards or struct patterns | match |
Ranges such as 10 to 19, or labels such as >= 90 | switch |
| One case should continue into the next | switch without break |
| Several statements per case, on a plain enum or value | either; both check that an enum is fully covered |
| A handful of unrelated conditions | switch true, or plain if and else |
Things to remember
- A match branch never falls through; a switch case always does, unless it ends with
break. - Enum patterns are case names, bare (
Spring) or with the enum in front (Season.Spring). - A match on an enum must cover every case, and so must a switch on an enum (E103), unless there is an
else. - A guarded branch, or a struct pattern with a test, does not cover its case on its own.
breakin a switch leaves the switch;break 2also leaves the loop around it.- In a switch,
elsemust be the last label.