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 fullGetting started
The smallest useful thing you can do with it, and what each part means.
rust
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.Advanced usage
Where the library earns its place over a simpler alternative.
rust
#[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)
}
}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.
