Generics and Tower
Axum’s generics allow independently written handlers, extractors, bodies, listeners, middleware, and runtimes to agree on contracts.
Service<Request> is the common language
Tower’s conceptual interface is:
trait Service<Request> {
type Response;
type Error;
type Future: Future<Output = Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>>;
fn call(&mut self, request: Request) -> Self::Future;
}
Layers transform one generic service type into another. Axum can therefore reuse timeouts, tracing, limits, and authorization without owning them.
How an async function implements Handler
Every async fn has an anonymous future type, and every argument list gives a
different function type. Axum implements Handler<T, S> for functions whose
arguments implement extraction traits, whose future is Send, and whose output
implements IntoResponse.
The T parameter encodes the argument tuple and marker type. It is partly a
coherence workaround that distinguishes blanket implementations which might
otherwise overlap.
Source: handler/mod.rs:140
A macro expands tuple arities, not runtime magic
impl_handler! generates the same implementation for supported argument
counts. At runtime it is ordinary code: split request, await each extractor,
call the function, convert the result. The macro compensates for the lack of
variadic generics.
Two extractor traits encode body ownership
FromRequestParts<S> receives mutable metadata and a shared state reference.
FromRequest<S> owns the complete request. All but the final handler argument
must use parts; the last may consume the body. A resource constraint becomes a
generic bound.
State is a missing type
Router<S> means “a router still missing state S,” not “a router currently
holding S.” Calling with_state supplies that value and can yield Router<()>,
which is serveable. State<Inner> uses FromRef<Outer> to derive focused
substate from the application’s outer state.
This catches incomplete wiring at compile time while letting libraries request only the state they need.
Static and dynamic dispatch meet in the middle
The public builder API retains concrete generic types for checking and composition. Internally, routes use a cloneable boxed service to store heterogeneous endpoints together. Good generic design chooses the boundary where type erasure makes the whole system usable.