Skip to content
← Back to explainers

Python explainer

Mark Functions That Never Return

About 2 min to read. Allow extra time to work through examples.

Annotate a helper that always raises with Never, so the type checker knows the code after the call does not run.

This article uses Python. You can make it your practice mode to browse related content across the site.

You write a helper that raises a validation error, so each check is one line. Then mypy rejects code that looks correct. Save this as checks.py:

class ValidationError(ValueError):
    pass


def fail(message: str) -> None:
    raise ValidationError(message)


def require_text(value: object) -> str:
    if not isinstance(value, str):
        fail("expected a string")
    return value
$ mypy --strict checks.py
checks.py:12: error: Incompatible return value type (got "object", expected "str")  [return-value]
Found 1 error in 1 file (checked 1 source file)

-> None tells mypy that fail can return normally. So mypy assumes that a value that is not a string can reach return value.

Annotate the helper with Never

Never is the return type of a function that never returns normally. It always raises, exits the program, or loops forever.

from typing import Never


class ValidationError(ValueError):
    pass


def fail(message: str) -> Never:
    raise ValidationError(message)


def require_text(value: object) -> str:
    if not isinstance(value, str):
        fail("expected a string")
    return value


assert require_text("ready") == "ready"

Now mypy knows that nothing after fail(...) runs. So at return value, value must be a str, and the error goes away.

The annotation is for the type checker only. Python does not check it when the code runs, so write a test that checks that fail raises.

NoReturn, also in typing, means the same as Never. Older code uses NoReturn, and Never arrived in Python 3.11. Pick one and use it across your project.

Use Never on a method that always raises

Return Errors as Values with a Result Type, at the end of this track, has two classes. Ok.unwrap() returns the stored value, and Err.unwrap() always raises. Annotate Err.unwrap() with -> Never. Then unwrap() on a result that might be either one has exactly the value's type. With -> None instead, every caller would have to handle a None that never comes.

Going further

assert_never uses Never to check that a match statement or an if chain handles every case. The assert_never exercise in the type hints track lets you practice it.

step 18 of 20

Error Handling with Exceptions, Retries and Warnings

Up next Type Hints for Code That Can Fail