Loading pageâŚ
Rust walkthroughs
Loading pageâŚ
tower::layer function enable composition of middleware layers in a type-safe way?tower::layer wraps middleware components into a Layer trait implementation that can be applied to services, enabling clean composition through the ServiceBuilder pattern. Layers transform one service into anotherâadding functionality like timeout handling, rate limiting, or loggingâwhile preserving type safety throughout the composition chain. The key insight is that Layer and Service are separate traits: Layer::layer(S) takes a Service and returns a new Service, allowing you to compose multiple layers into a single transformation pipeline. ServiceBuilder collects layers into a single composed layer that applies all transformations in order, with the Rust type system ensuring each layer's output matches the next layer's input requirements.
use tower::Service;
// A Service takes a request and returns a response
// This is the core abstraction in tower
pub 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, req: Request) -> Self::Future;
}
// Example: a simple service
struct EchoService;
impl Service<String> for EchoService {
type Response = String;
type Error = Infallible;
type Future = Ready<Result<String, Infallible>>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: String) -> Self::Future {
ready(Ok(req))
}
}Service is the fundamental abstraction: something that handles requests.
use tower::Service;
// A Layer wraps a service to add functionality
pub trait Layer<S> {
type Service: Service<Request>;
fn layer(&self, inner: S) -> Self::Service;
}
// Layers transform services:
// Layer<Service> -> NewServiceLayer transforms one service into another with added functionality.
use tower::{Service, layer_fn};
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
// A middleware that logs requests
struct LoggingService<S> {
inner: S,
}
impl<S, Request> Service<Request> for LoggingService<S>
where
S: Service<Request>,
{
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)
}
fn call(&mut self, req: Request) -> Self::Future {
println!("Processing request");
self.inner.call(req)
}
}
// Create a Layer using layer_fn
let logging_layer = layer_fn(|inner| LoggingService { inner });layer_fn creates a Layer from a closure that wraps a service.
use tower::ServiceBuilder;
use tower::timeout::TimeoutLayer;
use std::time::Duration;
// ServiceBuilder collects layers and applies them in order
let service = ServiceBuilder::new()
.layer(TimeoutLayer::new(Duration::from_secs(30)))
.service(my_service);
// The service now has timeout functionality
// Requests taking longer than 30 seconds will timeoutServiceBuilder provides a fluent API for composing layers.
use tower::ServiceBuilder;
use tower::limit::RateLimitLayer;
use tower::timeout::TimeoutLayer;
use std::time::Duration;
// Layers are applied in ORDER when calling .service()
let service = ServiceBuilder::new()
// First: Rate limiting is applied (outermost)
.layer(RateLimitLayer::new(10, Duration::from_secs(1)))
// Second: Timeout is applied (inner)
.layer(TimeoutLayer::new(Duration::from_secs(5)))
.service(my_service);
// Request flow: Request -> RateLimit -> Timeout -> MyService
// Response flow: MyService -> Timeout -> RateLimit -> Response
// For requests: outermost (first layer) to innermost (last layer)
// For responses: innermost to outermostLayers are applied in declaration order for requests, reversed for responses.
use tower::ServiceBuilder;
use tower::timeout::TimeoutLayer;
use std::time::Duration;
// Each layer transforms the service type
// The type system ensures compatibility
// Before: MyService
// After TimeoutLayer: TimeoutService<MyService>
// After RateLimitLayer: RateLimitService<TimeoutService<MyService>>
let service = ServiceBuilder::new()
.layer(TimeoutLayer::new(Duration::from_secs(5)))
.layer(RateLimitLayer::new(10, Duration::from_secs(1)))
.service(my_service);
// The Rust compiler verifies that each layer's output
// matches the next layer's input requirementsThe type system ensures each layer's output matches the next layer's expectations.
use tower::ServiceBuilder;
use tower::limit::RateLimitLayer;
use tower::timeout::TimeoutLayer;
use std::time::Duration;
// ServiceBuilder can be converted into a composed Layer
let layer = ServiceBuilder::new()
.layer(RateLimitLayer::new(10, Duration::from_secs(1)))
.layer(TimeoutLayer::new(Duration::from_secs(5)))
.into_layer(); // Returns impl Layer
// Apply the composed layer to multiple services
let service1 = layer.layer(service1);
let service2 = layer.layer(service2);
// Both services get the same middleware stackinto_layer() creates a reusable layer from a ServiceBuilder.
use tower::{Layer, Service};
use std::time::Duration;
// Custom middleware: adds a header to requests
struct AddHeaderLayer {
header_name: String,
header_value: String,
}
impl<S> Layer<S> for AddHeaderLayer {
type Service = AddHeaderService<S>;
fn layer(&self, inner: S) -> Self::Service {
AddHeaderService {
inner,
header_name: self.header_name.clone(),
header_value: self.header_value.clone(),
}
}
}
struct AddHeaderService<S> {
inner: S,
header_name: String,
header_value: String,
}
impl<S, Request> Service<Request> for AddHeaderService<S>
where
S: Service<Request>,
{
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)
}
fn call(&mut self, req: Request) -> Self::Future {
// In a real implementation, you'd modify the request here
// For this example, we just pass through
self.inner.call(req)
}
}Implement Layer to create reusable middleware components.
use tower::ServiceBuilder;
use tower::limit::{RateLimitLayer,ConcurrencyLimitLayer};
use tower::timeout::TimeoutLayer;
use tower::retry::RetryLayer;
use tower::load_shed::LoadShedLayer;
use std::time::Duration;
let service = ServiceBuilder::new()
// Handle overload by shedding load
.layer(LoadShedLayer::new())
// Limit concurrent requests
.layer(ConcurrencyLimitLayer::new(100))
// Rate limit: 100 requests per second
.layer(RateLimitLayer::new(100, Duration::from_secs(1)))
// Retry failed requests
.layer(RetryLayer::new(retry_policy))
// Timeout after 30 seconds
.layer(TimeoutLayer::new(Duration::from_secs(30)))
.service(my_service);
// All layers are type-checked at compile timeComplex middleware stacks are built by chaining layers.
use tower::ServiceBuilder;
use tower::layer::LayerFn;
// The identity layer passes through unchanged
let identity_layer = LayerFn::new(|service| service);
let service = ServiceBuilder::new()
.layer(identity_layer) // Does nothing
.service(my_service);
// Useful for conditional middleware
let layer = if config.enable_timeout {
TimeoutLayer::new(Duration::from_secs(30)).into_layer()
} else {
identity_layer.into_layer()
};Identity layer provides a pass-through when no middleware is needed.
use tower::ServiceBuilder;
use tower::timeout::TimeoutLayer;
use std::time::Duration;
// Each layer can add type constraints
trait HasTimeout {
fn timeout(&self) -> Duration;
}
// Layers propagate requirements through the type system
// The compiler will error if constraints aren't satisfied
// Example error if layers are incompatible:
// error[E0271]: type mismatch resolving `<TimeoutService<MyService> as Service<Request>>::Response == <RateLimitService<MyService> as Service<Request>>::Response`
let service = ServiceBuilder::new()
.layer(TimeoutLayer::new(Duration::from_secs(5)))
.service(my_service);Type constraints flow through the composition chain.
use tower::{Layer, Service};
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
// Layer that shares state between services
struct CounterLayer {
count: Arc<AtomicU64>,
}
impl<S> Layer<S> for CounterLayer {
type Service = CounterService<S>;
fn layer(&self, inner: S) -> Self::Service {
CounterService {
inner,
count: Arc::clone(&self.count),
}
}
}
struct CounterService<S> {
inner: S,
count: Arc<AtomicU64>,
}
impl<S, Request> Service<Request> for CounterService<S>
where
S: Service<Request>,
{
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)
}
fn call(&mut self, req: Request) -> Self::Future {
self.count.fetch_add(1, Ordering::Relaxed);
self.inner.call(req)
}
}
// Use the layer
let counter = Arc::new(AtomicU64::new(0));
let layer = CounterLayer { count: counter.clone() };
let service = layer.layer(my_service);Layers can share state across services they wrap.
use tower::ServiceBuilder;
use tower::timeout::TimeoutLayer;
use tower::limit::RateLimitLayer;
use std::time::Duration;
// Define a reusable layer stack
let common_layers = ServiceBuilder::new()
.layer(RateLimitLayer::new(100, Duration::from_secs(1)))
.layer(TimeoutLayer::new(Duration::from_secs(30)));
// Convert to Layer
let layer = common_layers.into_layer();
// Apply to multiple services
let user_service = layer.clone().layer(user_service);
let product_service = layer.clone().layer(product_service);
let order_service = layer.layer(order_service);
// All services get the same middleware stack
// But layer.clone() requires Layer: CloneLayers can be cloned and applied to multiple services.
use tower::ServiceBuilder;
use tower::ServiceExt;
// For simple transformations, use and_then
let service = my_service
.and_then(|response| async move {
// Transform response
Ok(transformed_response)
});
// This creates a new service without defining a Layer
// Useful for one-off transformationsand_then provides simple middleware without creating a Layer.
use tower::ServiceBuilder;
use tower::timeout::TimeoutLayer;
use std::time::Duration;
// Direct wrapping (one service)
let wrapped = Timeout::new(my_service, Duration::from_secs(30));
// Using Layer (reusable pattern)
let layer = TimeoutLayer::new(Duration::from_secs(30));
let wrapped1 = layer.layer(service1);
let wrapped2 = layer.layer(service2);
// Using ServiceBuilder (multiple layers)
let wrapped = ServiceBuilder::new()
.layer(TimeoutLayer::new(Duration::from_secs(30)))
.service(my_service);Layers provide reusability; direct wrapping is simpler for one-off cases.
use tower::ServiceBuilder;
use tower::timeout::TimeoutLayer;
use std::time::Duration;
// Errors can be transformed through layers
// TimeoutLayer converts timeout errors to the service's error type
let service = ServiceBuilder::new()
.layer(TimeoutLayer::new(Duration::from_secs(5)))
.service(my_service);
// If the inner service returns MyError, the timeout layer returns
// Box<dyn Error> or a compatible error type
// Layer error types must be compatibleError types must be compatible through the layer chain.
use tower::ServiceBuilder;
use tower::timeout::TimeoutLayer;
use tower::limit::RateLimitLayer;
use std::time::Duration;
// Combine multiple ServiceBuilders
let common_layers = ServiceBuilder::new()
.layer(RateLimitLayer::new(100, Duration::from_secs(1)));
let service = ServiceBuilder::new()
.layer(TimeoutLayer::new(Duration::from_secs(30)))
.service(common_layers.service(my_service));
// Or use into_layer to compose
let common = common_layers.into_layer();
let service = ServiceBuilder::new()
.layer(TimeoutLayer::new(Duration::from_secs(30)))
.layer(common)
.service(my_service);ServiceBuilder instances can be combined for modular composition.
use tower::ServiceBuilder;
use tower::limit::{RateLimitLayer, ConcurrencyLimitLayer};
use tower::timeout::TimeoutLayer;
use tower::load_shed::LoadShedLayer;
use tower::retry::RetryLayer;
use tower::buffer::BufferLayer;
use std::time::Duration;
// Production-ready service stack
let service = ServiceBuilder::new()
// Shed load when overloaded
.layer(LoadShedLayer::new())
// Buffer requests for async handling
.layer(BufferLayer::new(1024))
// Limit concurrent requests
.layer(ConcurrencyLimitLayer::new(1000))
// Rate limit requests
.layer(RateLimitLayer::new(100, Duration::from_secs(1)))
// Timeout requests
.layer(TimeoutLayer::new(Duration::from_secs(30)))
.service(my_http_service);
// Type: LoadShedService<BufferService<ConcurrencyLimitService<RateLimitService<TimeoutService<MyService>>>>>Real-world services combine many layers for reliability.
use tower::ServiceBuilder;
use tower::layer::{Layer, LayerFn};
use tower::timeout::TimeoutLayer;
use std::time::Duration;
fn create_layers(enable_timeout: bool, enable_rate_limit: bool) -> impl Layer<MyService> {
let mut builder = ServiceBuilder::new();
if enable_timeout {
builder = builder.layer(TimeoutLayer::new(Duration::from_secs(30)));
}
if enable_rate_limit {
builder = builder.layer(RateLimitLayer::new(100, Duration::from_secs(1)));
}
builder.into_layer()
}
// Note: Rust's type system requires all branches to return the same type
// For truly dynamic layers, you may need Box<dyn Layer> or enum layersConditional layers can be configured at runtime.
use tower::{Layer, Service};
use std::future::Future;
use std::pin::Pin;
struct AsyncMiddlewareLayer;
impl<S> Layer<S> for AsyncMiddlewareLayer {
type Service = AsyncMiddlewareService<S>;
fn layer(&self, inner: S) -> Self::Service {
AsyncMiddlewareService { inner }
}
}
struct AsyncMiddlewareService<S> {
inner: S,
}
impl<S, Request> Service<Request> for AsyncMiddlewareService<S>
where
S: Service<Request>,
S::Future: Send,
{
type Response = S::Response;
type Error = S::Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, req: Request) -> Self::Future {
let future = self.inner.call(req);
Box::pin(async move {
// Do async work before/after inner service
let response = future.await?;
Ok(response)
})
}
}Layers can perform async work by returning boxed futures.
use tower::{ServiceBuilder, Layer, Service};
use tower::timeout::TimeoutLayer;
use tower::limit::RateLimitLayer;
use std::time::Duration;
// Manual layering (verbose, error-prone)
let service1 = RateLimitService::new(
TimeoutService::new(my_service, Duration::from_secs(30)),
100,
Duration::from_secs(1)
);
// Must match the exact nesting order
// ServiceBuilder (clean, composable)
let service2 = ServiceBuilder::new()
.layer(RateLimitLayer::new(100, Duration::from_secs(1)))
.layer(TimeoutLayer::new(Duration::from_secs(30)))
.service(my_service);
// Reads top-to-bottom, executes outer-to-innerServiceBuilder provides cleaner syntax than manual nesting.
tower::layer and ServiceBuilder enable type-safe middleware composition through two key abstractions:
The Layer trait transforms services: given a service S, Layer::layer(S) returns a new service that wraps S with additional functionality. Layers are middleware factoriesâthey don't handle requests themselves but create services that do. This separation means a single layer can wrap many services.
The ServiceBuilder pattern collects layers and applies them in sequence. Each .layer() call adds to the composition chain, and .service() applies all layers to create the final service. The order matters: first layer added is outermost for requests, innermost for responses.
Type safety emerges from composition. Each layer's output type must match the next layer's input type. The Rust compiler verifies the entire chain at compile timeâif you add a layer that returns String but the next layer expects Request, you get a compile error. This prevents runtime failures from incompatible middleware.
Key insight: The power of this pattern is that layers compose cleanly without runtime overhead. A stack of ten layers is a single composed type with no virtual calls or allocations. The middleware logic is fully inlined, and the type system ensures correctness. This makes tower's layer system suitable for high-performance systems where every nanosecond matters.