Skip to content
← All articles

NoReturn, Never, and functions that do not come back

A cross-reference to items 1.3 and 1.4, plus the one thing that belongs in a failure track: the `-> NoReturn` helper idiom, and why annotating a raising method `-> None` instead of `-> Never` quietly poisons every caller's narrowing.

This is a short item. NoReturn and Never are owned by the type-system track — 1.3 (the bottom type and where it comes from) and 1.4 (exhaustiveness checking with assert_never). Go there for the theory, the variance story, and the match exhaustiveness pattern.

What belongs here, in a track about failure, is one idiom and one consequence.

The idiom

A helper whose entire job is to raise:

from typing import NoReturn

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

Annotate it -> NoReturn and every caller’s control-flow analysis gets it right:

def parse_port(raw: str) -> int:
    if not raw.isdigit():
        fail(f"port must be numeric, got {raw!r}")
    return int(raw)      # mypy knows this line is only reached when raw is numeric

Annotate it -> None instead and mypy believes fail() can return, so it believes parse_port can fall through, and now you are fighting the checker about a branch that cannot happen. The annotation is not documentation; it is the fact that makes narrowing work.

The consequence

The same reasoning applies to any method that cannot return. In this track’s Result problem, Err.unwrap() is annotated -> Never:

def unwrap(self) -> Never:
    raise UnwrapError(...)

On the union Ok[str, E] | Err[str, E], mypy joins str with Never and gets str — because Never is the bottom type and contributes nothing to a union. The happy path keeps its exact type even though one arm cannot return.

Annotate it -> None and the join becomes str | None. Every caller now has to handle a None that can never occur, and your carefully typed Result has made the code worse than the exceptions it replaced. One wrong annotation, and the abstraction stops paying for itself.

NoReturn and Never are spellings of the same type; Never (3.11+) is preferred in new code, NoReturn remains for return annotations and reads better there.

→ See 1.3 and 1.4 for the full treatment.