rgoussu@goussu: ~/library/rust/web-frameworks
~/library/rust/web-frameworks cat tower-hyper-correspondence.md

Frameworks × tower/hyper — who builds on the contracts

# Contract-by-contract table of how Axum, Actix Web, Rocket, warp and poem honor, wrap, or replace the tower/hyper substrate, and why contract fidelity pays.

Conceptsaved 2026-08-09 #rust#web#frameworks#tower#hyper#interoperability

Overview

Java's version of this question is spec-shaped (Frameworks × Jakarta EE); Go's is stdlib-shaped (Frameworks × the stdlib contracts). Rust's contracts are community crates that won: the http crate's Request/Response types, hyper as the HTTP engine, and tower's Service/Layer traits as the middleware contract — de-facto standards with no spec body and no stdlib blessing behind them. The same three postures appear: honor the contracts (everything composes), wrap them, or bring your own stack. This note tabulates who does what.

Key points

  • Axum is the "all ✔" column — it exists to prove the substrate composes; every tower Layer ever written applies to an axum Router directly.
  • Actix Web is the "own stack" column — its own engine and middleware trait; not worse, just separate: integrations arrive as actix-* crates rather than falling out of tower.
  • Rocket is a hybrid — hyper underneath since 0.5, but its own middleware (fairings), guards and types above; substrate benefits stop at the engine.
  • The contract that matters most is tower::Service: it is how the observability and resilience ecosystem ships (tower-http tracing/CORS/compression, timeouts, rate/concurrency limits) — and it reaches beyond HTTP: tonic speaks tower too, so one middleware stack can cover REST and gRPC.
  • http crate types are the quiet universal: axum, warp, poem, tonic, reqwest all speak http::Request/Response; actix and Rocket convert at their edges. Cross-cutting libraries converge anyway — serde everywhere, tracing as the logging/telemetry substrate in all five stacks.
  • The payoff is compounding: like func(http.Handler) http.Handler in Go, a tower Layer written once (auth, tracing, backpressure) serves every tower-honoring framework, client (reqwest-middleware is tower-flavoured) and protocol; own-stack frameworks re-implement per framework.

Details

Contract-by-contract

✔ honored natively · ≈ wrapped/adapted · ✘ replaced

Contract Axum Actix Web Rocket warp poem
http crate types ✔ throughout ✘ own HttpRequest/HttpResponse ≈ own types, converts internally
hyper as engine actix-http ✔ (internal detail)
tower::Service middleware ✔ native (.layer) ✘ own Transform trait ✘ fairings ≈ own Filter model; tower adapters exist ✔ (own + tower compat layer)
tower-http catalog (trace, CORS, compression…) ✔ direct ✘ actix-* equivalents ✘ fairing/contrib equivalents ≈ via adapters
Runtime Tokio Tokio (via actix-rt worker model) Tokio Tokio Tokio
Testing without sockets Router is a Serviceoneshot actix_web::test::init_service rocket::local::asynchronous::Client ✔ filters callable directly
Ecosystem gravity the default; tonic/utoipa/tower all point here large legacy + own contrib ring own ring, smaller maintenance mode-ish; niche growing, OpenAPI-first niche

Reading the table

The pattern rhymes with Go's: axum : chi :: actix : Fiber :: Rocket : Gin — roughly. The difference is that Rust's "stdlib contract" is itself a community crate: tower could in principle be displaced (and pre-1.0 hyper churn burned early adopters), so the bet is on the Tokio team's stewardship rather than on a standards body or a stdlib freeze. So far the bet has compounded: gRPC (tonic), HTTP clients (reqwest), service meshes (linkerd's proxy) and axum all sharing one middleware abstraction is something neither Go (server-only contract) nor Java (per-spec middleware) quite achieves.

Related