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
Layerever written applies to an axumRouterdirectly. - 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. httpcrate types are the quiet universal: axum, warp, poem, tonic, reqwest all speakhttp::Request/Response; actix and Rocket convert at their edges. Cross-cutting libraries converge anyway — serde everywhere,tracingas the logging/telemetry substrate in all five stacks.- The payoff is compounding: like
func(http.Handler) http.Handlerin Go, a towerLayerwritten 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 Service — oneshot |
≈ 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
- Rust web frameworks — the landscape — parent overview of the frameworks compared here.
- Axum deep dive — the honors-everything column.
- Actix Web deep dive — the own-stack column.
- Rocket deep dive — the hybrid.
- gRPC in Rust — tonic: the same contracts beyond HTTP APIs.
- Frameworks × Jakarta EE — who implements what — the spec-shaped version of this question.
- Frameworks × the stdlib contracts — the stdlib-shaped version.