Contents
Chapter 13

Pattern Matching

The match statement compares a value against a series of patterns and runs the first one that fits. A match is far more than a switch because a pattern can test a value’s shape, look inside it, and pull out the parts you need, all in one step. No C-style switch can express this:

match event:
    case {"type": "click", "x": x, "y": y}:
        ...

That case matches only a dictionary whose "type" is "click", and it binds x and y from that dictionary as it matches. match becomes valuable once the patterns do more than test equality.

Pattern matching first appeared in Control Flow.

match and case are soft keywords: they act as keywords only inside this statement, so existing code that uses match as a variable name still runs. Avoid naming a new variable match: the name shadows no keyword, but it reads like the statement.

Matching Values

The simplest patterns are literal values. A case _ at the end is the wildcard. It matches anything, like a default. Without one, a match that fits no pattern does nothing and raises no error. match tries the patterns top to bottom and the first match wins: one case body runs, then the statement ends. Unlike C’s switch, cases do not fall through, and match has no break to forget:

# http_status.py

def describe(status: int) -> str:
    match status:
        case 200:
            return "OK"
        case 404:
            return "Not Found"
        case 500:
            return "Server Error"
        case _:
            return f"Status {status}"

print(describe(200))
#: OK
print(describe(404))
#: Not Found
print(describe(301))
#: Status 301

A literal pattern compares with ==, not with is, so case 200: also matches 200.0 and case 1: matches True. None, True, and False are the exception: those three compare with is, so case True: does not match 1.

For a value-to-value lookup like this, a dictionary is often shorter (see When Not to Match).

Alternatives and Capture

An alternative combines several patterns in one case with |. Each alternative joined by | must bind the same set of names.

A bare name is a capture pattern. Like _, it matches any value unconditionally. Unlike _, it also binds the matched value to that name. Here, other is the capture pattern:

# step.py

def step(command: str) -> str:
    match command:
        case "up" | "u":
            return "y -= 1"
        case "down" | "d":
            return "y += 1"
        case other:
            return f"unknown command: {other}"

print(step("up"))
#: y -= 1
print(step("d"))
#: y += 1
print(step("jump"))
#: unknown command: jump

A bare name always binds. It does not compare against a variable of that name, so a named constant in a case silently captures instead. A value pattern is a dotted name, and it does compare. Signal is an Enum (Data Classes as Types introduces them):

# value_patterns.py
from enum import Enum
from typing import Final

class Signal(Enum):
    STOP = "stop"
    GO = "go"

DEFAULT: Final[Signal] = Signal.STOP

def act(s: Signal) -> str:
    match s:
        case Signal.GO:
            return "accelerate"
        case Signal.STOP:
            return "brake"

def broken(s: Signal) -> str:
    match s:
        case DEFAULT:
            return f"DEFAULT is now {DEFAULT}"
    return "unreachable"

print(act(Signal.GO), act(Signal.STOP))
#: accelerate brake
print(broken(Signal.GO))
#: DEFAULT is now Signal.GO
print(DEFAULT)
#: Signal.STOP

case Signal.GO compares. case DEFAULT binds: it matches Signal.GO, rebinds DEFAULT as a local name inside broken(), and leaves the module-level constant untouched. Python catches the mistake when a later case follows a bare-name capture, refusing to compile with SyntaxError: name capture 'DEFAULT' makes remaining patterns unreachable. When the capture is the last case, as here, Python does not warn you, and neither ty nor ruff catches it either: the case binds a new local named DEFAULT instead of comparing against the module constant, and nothing in the toolchain says so.

act() also shows why an enum is worth the trouble: Signal is a closed set, so the type checker sees that the cases cover both members and accepts the function with no trailing return.

Sequence Patterns

A sequence pattern matches the shape of a list or tuple and binds the elements by position. A starred name, as in *rest, captures the remaining elements:

# sequence_patterns.py

def summarize(items: list[int]) -> str:
    match items:
        case []:
            return "Empty"
        case [only]:
            return f"One item: {only}"
        case [first, second]:
            return f"Two items: {first}, {second}"
        case [first, *rest]:
            return f"{first}, then {len(rest)} more"
        case _:
            return "Unreachable"

def last_of(items: list[int]) -> tuple[list[int], int]:
    match items:
        case [*init, last]:
            return init, last
        case _:
            return [], 0

print(summarize([]))
#: Empty
print(summarize([5]))
#: One item: 5
print(summarize([3, 4]))
#: Two items: 3, 4
print(summarize([1, 2, 3, 4]))
#: 1, then 3 more
print(last_of([1, 2, 3, 4]))
#: ([1, 2, 3], 4)

summarize() shows the structural part of “structural pattern matching.” The pattern [first, second] matches only a two-element sequence and pulls both out at once. The last case _ never runs: [first, *rest] catches every nonempty list and [] the empty one. The type checker cannot prove that, so the wildcard stays to satisfy the declared return type.

A starred name can appear anywhere in a sequence pattern, not only at the end, as long as the pattern has no more than one. last_of() puts it first: [*init, last] binds every element but the last to init. [first, *middle, last] would put it in the middle instead, binding the two ends by name and everything between them to middle.

A sequence pattern deliberately excludes str, bytes, and bytearray. case [a, b, c] does not match "abc", even though a string is a sequence in every other context. Iterating a string a character at a time is rarely what a pattern means, so the language rules it out. A tuple does match. case [a, b, c] accepts (1, 2, 3) as readily as [1, 2, 3], because the pattern describes a shape, not a concrete type. Parentheses group a pattern rather than build a tuple. case (x) is case x, an unconditional capture, and case (x,) is a one-element sequence pattern. The subject must be a sequence, though, not merely iterable: case [a, b] matches a range but not a generator and not a set.

The brackets are optional in a sequence pattern, so case 0, 0: and case [0, 0] are the same pattern. The subject is any expression, not only a parameter, and a comma builds a tuple there too, so match sign(x), sign(y): matches on a pair computed inline. Transforming the subject this way turns a set of comparisons into literal patterns, and that usually reads better than the guards you would write otherwise.

# test_sequence_patterns.py
import pytest
from sequence_patterns import summarize

@pytest.mark.parametrize("items, expected", [
    ([], "Empty"),
    ([5], "One item: 5"),
    ([1, 2, 3], "1, then 2 more"),
])
def test_sequence_patterns(items: list[int],
                           expected: str) -> None:
    assert summarize(items) == expected

Class Patterns

A class pattern matches by type and extracts attributes. With a data class you can match positionally or by keyword:

# point.py
from dataclasses import dataclass

@dataclass(frozen=True)
class Point:
    x: int
    y: int
# class_patterns.py
from point import Point

def locate(p: Point) -> str:
    match p:
        case Point(0, 0):
            return "The origin"
        case Point(0, y):
            return f"On the y-axis at y={y}"
        case Point(x, 0):
            return f"On the x-axis at x={x}"
        case Point(x, y):
            return f"At ({x}, {y})"

print(locate(Point(0, 0)))
#: The origin
print(locate(Point(0, 5)))
#: On the y-axis at y=5
print(locate(Point(3, 0)))
#: On the x-axis at x=3
print(locate(Point(3, 4)))
#: At (3, 4)

Point(0, 0) matches a point whose fields are both zero. Point(0, y) matches when x is zero and captures y. The literal and the capture combine in one pattern.

Despite the call syntax, a class pattern builds nothing: it tests the subject’s type and reads its attributes. Positional matching depends on __match_args__, a class attribute listing field names in order. @dataclass generates it automatically from the field order, so Point(0, y) means “position 0 is x, position 1 is y.” NamedTuple generates it too. An ordinary class must assign it by hand. A positional pattern raises a TypeError when __match_args__ is too short to name every position you supply. For an ordinary class R that lacks one, case R(1) reports TypeError: R() accepts 0 positional sub-patterns (1 given).

Keyword patterns work differently. Point(x=0, y=y) matches by attribute name, through attribute access, not through __match_args__. Keyword patterns also work on any object with the named attributes, data class or not, and let you match a subset of attributes while ignoring the rest:

# keyword_patterns.py
from point import Point

def describe(p: Point) -> str:
    match p:
        case Point(x=0):
            return "Somewhere on the y-axis"
        case Point(y=0):
            return "Somewhere on the x-axis"
        case Point():
            return "Just some point"

print(describe(Point(0, 5)))
#: Somewhere on the y-axis
print(describe(Point(3, 0)))
#: Somewhere on the x-axis
print(describe(Point(3, 4)))
#: Just some point

Point(x=0) matches any point whose x attribute is zero, ignoring y. A positional pattern can leave fields unchecked too: Point(0) supplies fewer sub-patterns than __match_args__ names, so it ignores y, and Point(_, 0) uses the wildcard to skip x. Naming the attribute is clearer, and it survives a change to the field order. Reordering the fields rewrites __match_args__, so every positional pattern silently starts matching a different field. Point() with no arguments, keyword or positional, matches any Point instance. Use it as a type-only check or a final catch-all.

The type test is isinstance(), so a subclass matches its base’s pattern:

# type_patterns.py

def describe(value: object) -> str:
    match value:
        case bool(b):
            return f"bool {b}"
        case int(n):
            return f"int {n}"
        case str(s):
            return f"str of length {len(s)}"
        case _:
            return "something else"

print(describe(True))
#: bool True
print(describe(7))
#: int 7
print(describe("hello"))
#: str of length 5
print(describe(3.5))
#: something else

Because a subclass matches, the order of the cases decides which one wins. bool is a subclass of int, so moving case bool(b) below case int(n) makes it unreachable: describe(True) would answer int True.

In int(n), the positional sub-pattern binds the whole value rather than an attribute. Python special-cases a handful of builtins this way (bool, int, float, str, bytes, bytearray, list, tuple, dict, set, frozenset), so case str(s) reads as “a string, call it s.”

Dropping the parentheses flips the meaning: case str: is a bare-name capture, not a type test, matching any value and binding it to a local named str. case str: repeats the DEFAULT mistake from value_patterns.py, and Python catches it the same way, by refusing to compile any case after it: SyntaxError: name capture 'str' makes remaining patterns unreachable.

Matching on isinstance() is the opposite of the exact-type dispatch that a dict keyed on type(value) performs. Multiple Dispatching relies on that dispatch, and there a subclass finds no entry at all.

# test_class_patterns.py
import pytest
from class_patterns import locate
from keyword_patterns import describe
from point import Point

@pytest.mark.parametrize("point, expected", [
    (Point(0, 0), "The origin"),
    (Point(3, 0), "On the x-axis at x=3"),
    (Point(3, 4), "At (3, 4)"),
])
def test_class_patterns(point: Point,
                        expected: str) -> None:
    assert locate(point) == expected

@pytest.mark.parametrize("point, expected", [
    (Point(0, 5), "Somewhere on the y-axis"),
    (Point(3, 0), "Somewhere on the x-axis"),
    (Point(3, 4), "Just some point"),
])
def test_keyword_patterns(point: Point,
                          expected: str) -> None:
    assert describe(point) == expected

Guards

A guard is an if attached to a case. The case matches only when the pattern fits and the guard is true:

# guards.py
from point import Point

def quadrant(p: Point) -> str:
    match p:
        case Point(0, 0):
            return "Origin"
        case Point(x, y) if x > 0 and y > 0:
            return "First quadrant"
        case Point(x, y) if x < 0 and y > 0:
            return "Second quadrant"
        case _:
            return "Somewhere else"

def leaky(p: Point) -> int:
    match p:
        case Point(x, _) if x > 100:
            return 0
        case _:
            return x

print(quadrant(Point(0, 0)))
#: Origin
print(quadrant(Point(3, 4)))
#: First quadrant
print(quadrant(Point(-3, 4)))
#: Second quadrant
print(quadrant(Point(-1, -1)))
#: Somewhere else
print(leaky(Point(3, 4)))
#: 3

The guard runs after the pattern matches, so it can use the names the pattern bound. When a guard is false, match moves on to the next case, but the names stay bound. Once case Point(x, y) if x > 0 and y > 0 has failed, x and y still hold the values it captured. leaky() shows why that matters. case _: binds nothing, yet x still holds 3, left over from the failed guard in the case above it. A case that does not rebind a name inherits whatever an earlier, failed case left behind. A pattern tests shape and equality, so everything beyond that belongs in the guard: an ordering test like x > 0, a relation between two captures like x == y, or any call like len(items) > 3. Repeating a name does not express equality. case [x, x]: fails with SyntaxError: multiple assignments to name 'x' in pattern, so an equal-elements test is also a guard, case [x, y] if x == y:. A guard that merely compares one capture to a constant is a literal pattern written the long way.

Mapping Patterns

A mapping pattern matches keys in a dictionary and binds their values. It ignores keys you do not mention, so it dispatches cleanly on JSON-shaped data. Ignoring unmentioned keys also makes case {} a catch-all for any mapping rather than a test for an empty one, the opposite of case [], which matches only an empty sequence. Test for an empty dictionary with a guard, case {} if not event:. A **rest at the end binds whatever keys the pattern did not mention, the mapping counterpart of *rest in a sequence pattern.

# mapping_patterns.py

def handle(event: dict[str, object]) -> str:
    match event:
        case {"type": "click", "x": x, "y": y}:
            return f"Click at ({x}, {y})"
        case {"type": "key", "key": key}:
            return f"Key {key}"
        case {"type": kind}:
            return f"Other event: {kind}"
        case unknown:
            return f"Unrecognized event: {unknown}"

print(handle({"type": "click", "x": 10, "y": 20}))
#: Click at (10, 20)
print(handle({"type": "key", "key": "Enter"}))
#: Key Enter
print(handle({"type": "scroll", "delta": 3}))
#: Other event: scroll
print(handle({"button": 1}))
#: Unrecognized event: {'button': 1}

The test checks a matched event and the fall-through:

# test_mapping_patterns.py
from mapping_patterns import handle

def test_mapping_patterns() -> None:
    assert handle(
        {"type": "key", "key": "Esc"}) == "Key Esc"
    assert handle(
        {"nope": 1}) == "Unrecognized event: {'nope': 1}"

Binding through a mapping pattern loses type information a class pattern keeps. handle()’s parameter is event: dict[str, object], so x and y come out typed object, the same as every other value the dictionary could hold. class_patterns.py’s Point(x, y) binds x and y as int, because Point declares its fields that way. The shape test is precise, but each binding takes the dictionary’s one declared value type. When the data has a known shape, parse it into a dataclass first, then match on the dataclass: you keep the shape test and gain the field types.

Patterns Nest

Each section so far introduced one pattern form on its own. A sub-pattern is itself a pattern, so any of these forms can sit inside any other:

# nested_patterns.py
from point import Point

def survey(points: list[Point]) -> str:
    match points:
        case [Point(0, 0) as start, *rest]:
            return f"{start} then {len(rest)} more"
        case [Point(0, n) | Point(n, 0)]:
            return f"one axis point, offset {n}"
        case [Point(), Point()]:
            return "two points"
        case _:
            return "nothing to say"

print(survey([Point(0, 0), Point(1, 1), Point(2, 2)]))
#: Point(x=0, y=0) then 2 more
print(survey([Point(0, 5)]))
#: one axis point, offset 5
print(survey([Point(4, 0)]))
#: one axis point, offset 4
print(survey([Point(1, 2), Point(3, 4)]))
#: two points

The first case is a sequence pattern holding a class pattern holding two literals, with a starred capture beside it. as binds whatever its sub-pattern matched, so start is the whole Point while 0, 0 checks its fields. Without as you must choose between testing the shape and keeping the object.

The second case alternates two class patterns and binds n from either, inside a one-element sequence pattern: survey([Point(0, 5)]) matches, but a list of two points does not. The compiler enforces the same-names rule from Alternatives and Capture. Adding a third alternative | Point(1, 1), which binds nothing, fails with SyntaxError: alternative patterns bind different names.

A pattern can also nest inside a copy of its own case, matching a self-referential type such as a tree. Composite and Interpreter walks an expression tree this way: each case matches one node type and recurses into that node’s own children.

Exhaustive Matching

When a value is one of a fixed set of types, define that set as a union using the type statement. Now you can match on that union. When you end with case _: assert_never(value), the type checker ensures the match is exhaustive. If you add a type to the union without its case, the type checker reports an error at assert_never() instead of letting the value fall through at runtime. That is the benefit of static typing applied to control flow:

# exhaustive.py
from dataclasses import dataclass
from math import pi
from typing import assert_never

@dataclass(frozen=True)
class Circle:
    radius: float

@dataclass(frozen=True)
class Square:
    side: float

type Shape = Circle | Square

def area(shape: Shape) -> float:
    match shape:
        case Circle(radius):
            return pi * radius ** 2
        case Square(side):
            return side ** 2
        case _:
            assert_never(shape)

print(round(area(Circle(1.0)), 4))
#: 3.1416
print(area(Square(2.0)))
#: 4.0

If you add a Triangle to Shape without adding the appropriate case, the type checker flags assert_never(shape). assert_never() acts at runtime as well as at check time. If a value that lied about its type reaches assert_never(), the call raises AssertionError: Expected code to be unreachable, but got: 'x', naming the value it received.

A switch in C, JavaScript, or traditional Java has no such check: nothing forces you to add a case, and an unhandled value falls through silently. Scala’s match, Kotlin’s when, and Java’s newer switch expressions check exhaustiveness, as an error in Java and Kotlin and a warning in Scala. The check applies only when the matched type is a closed set the compiler can see in full: a sealed hierarchy or an enum. Their versions are also expressions, producing a value you can assign. Python’s match is a statement, not an expression, so a match that must produce a value goes inside a function that returns from each case.

Python has no sealed keyword. assert_never() plus a type checker fills that role instead. An if/isinstance() chain can reach the same guarantee, but only if you remember to end it with assert_never(). A match makes the shape of the dispatch explicit.

Shape turns the classic OOP “shapes” example into a closed type union instead of a class hierarchy. Dynamic Binding vs. Pattern Matching compares the two approaches directly.

The second test below exercises that runtime backstop. The string "x" is no Shape, so the call carries a # type: ignore. At runtime assert_never() catches it:

# test_exhaustive.py
import pytest
from exhaustive import Circle, Square, area

def test_exhaustive_area() -> None:
    assert round(area(Circle(1.0)), 4) == 3.1416
    assert area(Square(2.0)) == 4.0

def test_assert_never_rejects_a_lying_value() -> None:
    with pytest.raises(AssertionError):
        area("x")  # type: ignore

When Not to Match

For a value-to-value lookup, a dictionary is shorter:

# value_to_value_lookup.py
from typing import Final

STATUS: Final[dict[int, str]] = {
    200: "OK", 404: "Not Found", 500: "Server Error"}

def describe(status: int) -> str:
    try:
        return STATUS[status]
    except KeyError:
        return f"Status {status}"

print(describe(200))
#: OK
print(describe(301))
#: Status 301

The lookup is try/except rather than STATUS.get(status, f"Status {status}") because Python evaluates arguments before the call: every lookup builds the default string, including the hits that discard it.

A literal match compiles to a chain of comparisons, one per case, so its cost grows with the number of cases. A dictionary lookup costs the same at any size. At three entries the difference is invisible. The dictionary wins as the table grows, and a dictionary is the only one of the two you can build or change at runtime.

When the set of types is open (anyone can add a new one), inheritance and dynamic binding work better than match. Each type carries its own behavior, so adding a type needs no change to a central match. Use match for a closed set of cases you want to handle in one place, especially when the cases need to look inside the value. When that closed set is a set of constants rather than a set of shapes, make it an Enum and match on its members, as value_patterns.py did. The enum hands the type checker the closed set, so assert_never() works without a type union.

Dynamic Binding vs. Pattern Matching

An alerting system sends a notification through one of three channels: email, SMS, or push. Every channel renders the notification into a message string for a recipient. Every channel also has a rough cost to send a message.

The inheritance answer declares both operations as abstract methods on a base class. Each channel is a subclass that implements them, and dynamic binding picks the correct implementation at each call:

# notifications_oo.py
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import override

class Notification(ABC):
    @abstractmethod
    def render(self, recipient: str) -> str: ...

    @abstractmethod
    def cost(self) -> float: ...

@dataclass(frozen=True)
class Email(Notification):
    subject: str

    @override
    def render(self, recipient: str) -> str:
        return f"Email to {recipient}: {self.subject}"

    @override
    def cost(self) -> float:
        return 0.001

@dataclass(frozen=True)
class Sms(Notification):
    body: str

    @override
    def render(self, recipient: str) -> str:
        return f"SMS to {recipient}: {self.body}"

    @override
    def cost(self) -> float:
        return 0.02

@dataclass(frozen=True)
class Push(Notification):
    title: str

    @override
    def render(self, recipient: str) -> str:
        return f"Push to {recipient}: {self.title}"

    @override
    def cost(self) -> float:
        return 0.0005

email = Email("Invoice ready")
sms = Sms("Code: 5821")
push = Push("New message")

print(email.render("Dana"))
#: Email to Dana: Invoice ready
print(sms.render("Dana"))
#: SMS to Dana: Code: 5821
print(push.render("Dana"))
#: Push to Dana: New message
print(round(email.cost() + sms.cost() + push.cost(), 4))
#: 0.0215

Notification names the shape every channel must have. @abstractmethod forces Email, Sms, and Push to define both render() and cost(). Leave one out, and the class stays abstract: instantiating it raises a TypeError.

A type union with match takes the opposite shape. The channels become plain data, and each operation is a free function that inspects the type:

# notifications_match.py
from dataclasses import dataclass
from typing import assert_never

@dataclass(frozen=True)
class Email:
    subject: str

@dataclass(frozen=True)
class Sms:
    body: str

@dataclass(frozen=True)
class Push:
    title: str

type Notification = Email | Sms | Push

def render(note: Notification, recipient: str) -> str:
    match note:
        case Email(subject):
            return f"Email to {recipient}: {subject}"
        case Sms(body):
            return f"SMS to {recipient}: {body}"
        case Push(title):
            return f"Push to {recipient}: {title}"
        case _:
            assert_never(note)

def cost(note: Notification) -> float:
    match note:
        case Email():
            return 0.001
        case Sms():
            return 0.02
        case Push():
            return 0.0005
        case _:
            assert_never(note)

email = Email("Invoice ready")
sms = Sms("Code: 5821")
push = Push("New message")

print(render(email, "Dana"))
#: Email to Dana: Invoice ready
print(render(sms, "Dana"))
#: SMS to Dana: Code: 5821
print(render(push, "Dana"))
#: Push to Dana: New message
print(round(cost(email) + cost(sms) + cost(push), 4))
#: 0.0215

render() and cost() each match over Notification and end with assert_never(), so the type checker confirms each match handles every case.

# test_notifications.py
import notifications_match as nm
import notifications_oo as no
import pytest

@pytest.mark.parametrize("oo, data", [
    (no.Email("Hi"), nm.Email("Hi")),
    (no.Sms("Hi"), nm.Sms("Hi")),
    (no.Push("Hi"), nm.Push("Hi")),
])
def test_oo_and_match_agree(
    oo: no.Notification, data: nm.Notification
) -> None:
    assert oo.render("Dana") == nm.render(data, "Dana")
    assert oo.cost() == nm.cost(data)

Try growing the system in each direction. First, add a new type: a Webhook channel. In the object version, you write one new subclass with its own render() and cost(), and nothing else changes. In the match version, you add a Webhook data class to the Notification union, and the type checker flags assert_never() in both render() and cost() until you add a case Webhook(...) to each.

Now try adding a new operation, priority(), that ranks channels by urgency. In the object version, every existing subclass needs a new method. In the match version, you write one new function with its own match, and the existing classes and functions stay untouched.

Adding a type is cheaper with inheritance. Adding an operation is cheaper with pattern matching. That is the open-set-versus-closed-set tradeoff from When Not to Match, worked out concretely. It also has a name: the expression problem. Rethinking Objects works through the same split with shapes, and Multiple Dispatching and Visitor explore it further.

Exercises

  1. Write classify(value) that uses match to return "empty list", "singleton", or "longer list" for lists, "point" for a Point, and "other" for anything else.
  2. Add a Rectangle type to exhaustive.py’s Shape union without adding its case. Run ty and read the error it reports at assert_never.
  3. Rewrite mapping_patterns.handle() to also accept a nested shape, such as {"type": "click", "at": {"x": x, "y": y}}, binding x and y from the inner dictionary.
  4. Add a Webhook channel to notifications_match.py: a data class with a url field, added to the Notification union. Run ty before adding its case to render() and cost(), and read the errors. Then add both cases and confirm ty passes.
  5. Rewrite guards.py’s quadrant() so it handles the third and fourth quadrants too. Then write it a second time with one case per sign combination, using | alternations and no guards, and say which version reads better.
  6. Give value_patterns.py’s Signal a third member, and write act() so that it compares against a module-level FALLBACK: Final[Signal]. Run it and confirm that the constant captures instead of comparing. Then fix it two ways, with a dotted name and with a guard.