Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Callable surface review

Status: Implemented (B31). Function values, closures, and consuming and stored closures own the contract.

Shipped since this review: explicit fallible shared-call erasure supports heterogeneous callback storage. Mutable/consuming erased modes and generator-containing stored environments remain B32; stored borrows remain B11.

Plenty needs callable values for callbacks, small local functions, and owned resource handoff. This review separates representation from ownership and avoids making every capability a prerequisite for writing an ordinary closure.

Decisions

SurfaceConcrete needDecision
def(...)A multiline function expressionKeep; the same typed function body as a named function
def [captures](../...)State without implicit sharing or allocationKeep explicit captures; infer the environment type locally
value, mut value, &value, &mut valueOwn or borrow state, with ordinary mutabilityTeach as the existing ownership and mutability rules composed together
Callable[[A], B] as a value typeStore different named functions in one registryKeep the code-address representation; it must not silently box captured state
F: Callable[[A], B]One reusable callback API for named and captured functionsPrefer this for generic consumers; infer F at calls
Closure[[A], B]Describe a factory’s concrete environment resultKeep for factories and specialized interfaces; remove it from the default consumer lesson
def once [captures]Transfer captured owners out of a callbackKeep the explicit consuming contract; do not infer it from body changes
OnceClosure[[A], B]Describe a factory returning a consuming environmentKeep as an advanced factory annotation
F: OnceCallable[[A], B]A task API that accepts all call modes and consumes its callbackKeep as an advanced constraint, never a stored value type

There are two representations: a code address and a concrete environment. There are two invocation contracts: borrowing an environment for reuse and consuming it. The public annotations describe these differences; they do not introduce four interchangeable forms of dynamically dispatched object.

Collapsing every value into Callable would require either a uniform erased environment with explicit storage, or a type spelling that sometimes denotes a code address and sometimes a producer-specific layout. Neither makes storage costs easier to predict. Keep the distinction visible at interfaces while letting ordinary local bindings infer it. OnceCallable could not be an ordinary method-only protocol today: function-call syntax and capture transfer remain compiler-checked operations. Ordinary protocols can now require a by-value self, but that alone does not describe callable invocation and captures.

Inferring once from moving a capture out would make a body edit change whether callers can invoke it again. An explicit word gives a local ownership contract, like the difference between an owned parameter and a reference. A resource handoff needs that distinction; an ordinary read-only callback does not.

What belongs in libraries

A registry of named callbacks needs only a collection of Callable values. A callback plus explicit state can already be a generic class with ordinary methods. Filtering, dispatching, and adapting these callbacks are library code; they need no new invocation syntax or builtin registry type. Concrete owned environments now use ordinary typed owner slots. Heterogeneous erasure still needs a separate representation and lifetime contract before library code can build an arbitrary closure registry.

The teaching path is named function values, then ordinary inferred closures, then a reusable generic callback API. Factory annotations, borrowed captures, and consuming callbacks are optional extensions for concrete use cases. The functions chapter marks that shorter path. Existing advanced lessons remain referenceable, so simplifying the entry path loses no examples.

This review does not claim that the current names must be permanent. It records why each retained distinction is necessary today, and replaces unnecessary consumer-specific annotations with the common generic interface.