Skip to content

Tower

Networking & ConcurrencyNetworkingRust

What it is

Tower defines a single Service abstraction — an async function from request to response — plus composable middleware for timeouts, retries, rate limiting and load balancing.

A Service takes a request and returns a future. A Layer wraps one Service in another, so middleware composes as ordinary values.

Installation

cargo add tower --features full

Getting started

The smallest useful thing you can do with it, and what each part means.

Composing middleware
use tower::{ServiceBuilder, ServiceExt};
use std::time::Duration;

let service = ServiceBuilder::new()
    .timeout(Duration::from_secs(10))
    .concurrency_limit(64)          // bound in-flight requests
    .rate_limit(100, Duration::from_secs(1))
    .retry(retry_policy)
    .service(inner);

// Order matters: layers listed first are outermost, so the timeout
// covers the retries rather than each individual attempt.
That ordering detail catches people out. A timeout inside the retry layer bounds one attempt; outside it, it bounds the whole operation — usually what you want.

Advanced usage

Where the library earns its place over a simpler alternative.

A custom layer
#[derive(Clone)]
struct RequestId<S> { inner: S }

impl<S, R> Service<Request<R>> for RequestId<S>
where S: Service<Request<R>> + Clone + Send + 'static, S::Future: Send {
    type Response = S::Response;
    type Error = S::Error;
    type Future = S::Future;

    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        self.inner.poll_ready(cx)   // propagate backpressure
    }

    fn call(&mut self, mut req: Request<R>) -> Self::Future {
        req.headers_mut().insert("x-request-id", uuid_header());
        self.inner.call(req)
    }
}
poll_ready is Tower's backpressure mechanism and must be forwarded. Swallowing it means a saturated downstream service never signals that it is overloaded.

Errors and fixes

The failures you are most likely to hit, and what actually resolves them.

The trait bounds are impossible to satisfy
Tower's generics are demanding. Use ServiceBuilder and tower-http layers rather than composing services by hand where possible.
Retries amplify load during an outage
Retrying without a budget or backoff turns a blip into an outage. Bound attempts and add jitter.

Best practices

  • Use tower-http's ready-made layers before writing your own.
  • Remember layer ordering: the first listed is outermost.
  • Always forward poll_ready in custom services or backpressure is lost.
  • Add concurrency_limit in front of anything with a bounded resource, such as a database pool.

Background

Why it exists, and what it was reacting to.

Tower is the reason Axum, Tonic and Hyper share middleware. By agreeing on one Service trait, the ecosystem gets retry and timeout layers that work everywhere rather than per framework.