Skip to content
← All articles

Typing the seam: Protocols in signatures

A pointer, not a lesson: the Protocol mechanics live in track 2. What this track adds is where the Protocol goes, and which of them belongs in a public signature.

This is a cross-reference. The machinery of Protocol — structural subtyping, variance, @runtime_checkable, callback protocols, protocol members that are attributes rather than methods — is taught in track 2, Structural Typing and the Hard Parts, and in the item on narrow Protocols versus wide ABCs in this track. Read those for the mechanics.

Two things belong here and nowhere else, both about placement rather than syntax.

Put the Protocol in the layer that consumes it. This is not a style preference; it is the mechanism by which the dependency inverts. A RateSource Protocol declared in application/ means adapters imports application and nothing imports back. The same Protocol declared next to its implementation in adapters/ forces application to import outward, and you have written an interface that buys nothing. The test of whether you got it right: can the inner layer be imported with the outer layer’s third-party dependencies missing?

Prefer the standard ABC in a public signature when one already exists. Accept concurrent.futures.Executor, not ThreadPoolExecutor. Accept collections.abc.Mapping, not dict. When a caller swaps a thread pool for a process pool — or, on 3.14, an InterpreterPoolExecutor — it is a one-line change in their composition root and the type checker validates it. A signature that names the concrete class turns that one-line change into a fork of your library.

The rule collapses to one sentence: the narrowest type that describes what you actually use, declared where it is used.