Loading page…
Rust walkthroughs
Loading page…
{"tool_call":"file.create","args":{"content":"# What is the purpose of tower::layer::Layer trait and how does it differ from directly wrapping services?
The Layer trait defines a factory that transforms one service into another, enabling middleware composition where each layer can add behavior without knowing about the inner service implementation. Directly wrapping services with structs requires creating the entire service stack upfront and hardcoding the composition, while Layer allows services to be created on-demand from inner services that may not yet exist. This matters when services are created per-connection (like in tower::Service servers where each incoming connection needs a fresh service instance) because layers can be applied to service factories that produce new services for each request cycle.
use tower::Service;
use std::task::{Context, Poll};
use std::pin::Pin;
// A simple service that returns a greeting
struct GreetingService;
impl Service<String> for GreetingService {
type Response = String;\n type Error = ();\n type Future = std::future::Ready<Result<String, ()>>;\n\n fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {\n Poll::Ready(Ok(()))\n }\n\n fn call(&mut self, name: String) -> Self::Future {\n std::future::ready(Ok(format!(\"Hello, {}!\", name)))\n }\n}\n\n// Middleware that adds logging\nstruct LoggingService<S> {\n inner: S,\n}\n\nimpl<S, Request> Service<Request> for LoggingService<S>\nwhere\n S: Service<Request>,\n{\n type Response = S::Response;\n type Error = S::Error;\n type Future = S::Future;\n\n fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {\n self.inner.poll_ready(cx)\n }\n\n fn call(&mut self, request: Request) -> Self::Future {\n println!(\"Received request\");\n self.inner.call(request)\n }\n}\n\nfn main() {\n // Direct wrapping: create inner service, then wrap\n let greeting = GreetingService;\n let logged = LoggingService { inner: greeting };\n \n // Service is created immediately\n // Wrapping is hardcoded\n}\n```\n\nDirect wrapping requires the inner service to exist when creating the wrapper.
## Basic Layer Implementation\n\n```rust\nuse tower::Service;\nuse tower::layer::Layer;\nuse std::task::{Context, Poll};\n\n// The middleware service (same as before)\nstruct LoggingService<S> {\n inner: S,\n}\n\nimpl<S, Request> Service<Request> for LoggingService<S>\nwhere\n S: Service<Request>,\n{\n type Response = S::Response;\n type Error = S::Error;\n type Future = S::Future;\n\n fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {\n self.inner.poll_ready(cx)\n }\n\n fn call(&mut self, request: Request) -> Self::Future {\n println!(\"Received request\");\n self.inner.call(request)\n }\n}\n\n// Layer creates the logging service from any inner service\nstruct LoggingLayer;\n\nimpl<S> Layer<S> for LoggingLayer {\n type Service = LoggingService<S>;\n\n fn layer(&self, inner: S) -> Self::Service {\n LoggingService { inner }\n }\n}\n\nfn main() {\n let greeting = GreetingService;\n \n // Use layer to create the wrapped service\n let logged = LoggingLayer.layer(greeting);\n \n // Layer can be applied to ANY service\n let logged_greeting = LoggingLayer.layer(GreetingService);\n let logged_other = LoggingLayer.layer(OtherService);\n}\n```\n\n`Layer` provides a factory that transforms any service into a wrapped version.
## Layer for Middleware Reuse\n\n```rust\nuse tower::Service;\nuse tower::layer::Layer;\nuse std::task::{Context, Poll};\n\nstruct TimeoutService<S> {\n inner: S,\n timeout: std::time::Duration,\n}\n\nimpl<S, Request> Service<Request> for TimeoutService<S>\nwhere\n S: Service<Request>,\n S::Future: Send + 'static,\n{\n type Response = S::Response;\n type Error = S::Error;\n type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;\n\n fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {\n self.inner.poll_ready(cx)\n }\n\n fn call(&mut self, request: Request) -> Self::Future {\n let future = self.inner.call(request);\n let timeout = self.timeout;\n \n Box::pin(async move {\n tokio::time::timeout(timeout, future).await\n .map_err(|_| panic!(\"timeout error\"))\n })\n }\n}\n\n// Layer captures configuration\nstruct TimeoutLayer {\n timeout: std::time::Duration,\n}\n\nimpl<S> Layer<S> for TimeoutLayer {\n type Service = TimeoutService<S>;\n\n fn layer(&self, inner: S) -> Self::Service {\n TimeoutService {\n inner,\n timeout: self.timeout,\n }\n }\n}\n\nfn main() {\n // Create layer with configuration\n let timeout = TimeoutLayer {\n timeout: std::time::Duration::from_secs(5),\n };\n \n // Apply to any service\n let with_timeout = timeout.layer(GreetingService);\n}\n```\n\n`Layer` captures middleware configuration once, applies to many services.
## Service Factory Pattern\n\n```rust\nuse tower::Service;\nuse tower::layer::Layer;\nuse std::task::{Context, Poll};\n\n// Service that needs fresh state per connection\nstruct ConnectionHandler {\n connection_id: u64,\n}\n\nimpl Service<Vec<u8>> for ConnectionHandler {\n type Response = Vec<u8>;\n type Error = ();\n type Future = std::future::Ready<Result<Vec<u8>, ()>>;\n\n fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {\n Poll::Ready(Ok(()))\n }\n\n fn call(&mut self, request: Vec<u8>) -> Self::Future {\n println!(\"Connection {} handling request\", self.connection_id);\n std::future::ready(Ok(request))\n }\n}\n\n// Factory that creates ConnectionHandler for each connection\nstruct ConnectionHandlerFactory;\n\nimpl Service<()> for ConnectionHandlerFactory {\n type Response = ConnectionHandler;\n type Error = ();\n type Future = std::future::Ready<Result<ConnectionHandler, ()>>;\n\n fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {\n Poll::Ready(Ok(()))\n }\n\n fn call(&mut self, _: ()) -> Self::Future {\n static COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);\n let id = COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst);\n std::future::ready(Ok(ConnectionHandler { connection_id: id }))\n }\n}\n\nfn main() {\n // Without Layer: direct wrapping doesn't work with factories\n // We can't wrap ConnectionHandlerFactory with LoggingService\n // because LoggingService expects a Service<Request>, not a factory\n \n // With Layer: we can transform what the factory produces\n // This is what tower::ServiceBuilder enables\n}\n```\n\n`Layer` works with service factories that create services on-demand.
## Composing Multiple Layers\n\n```rust\nuse tower::ServiceBuilder;\nuse tower::layer::Layer;\nuse std::time::Duration;\n\n// Assume these layers exist:\n// LoggingLayer, TimeoutLayer, RateLimitLayer\n\nfn main() {\n // Direct wrapping: manual composition\n // let service = RateLimitService {\n // inner: TimeoutService {\n // inner: LoggingService {\n // inner: GreetingService\n // },\n // timeout: Duration::from_secs(5),\n // },\n // rate: 100,\n // };\n // \n // This is verbose and requires all services created upfront\n \n // With Layer and ServiceBuilder:\n let service = ServiceBuilder::new()\n .layer(RateLimitLayer { rate: 100 })\n .layer(TimeoutLayer { timeout: Duration::from_secs(5) })\n .layer(LoggingLayer)\n .service(GreetingService);\n \n // Layers compose in declaration order\n // Request flows: RateLimit -> Timeout -> Logging -> Greeting\n // Response flows: Greeting -> Logging -> Timeout -> RateLimit\n}\n```\n\n`Layer` enables declarative composition through `ServiceBuilder`.
## Layer Stacks for Reuse\n\n```rust\nuse tower::ServiceBuilder;\nuse tower::layer::Layer;\nuse std::time::Duration;\n\n// Define a reusable layer stack\nfn production_stack() -> impl Layer<GreetingService> {\n ServiceBuilder::new()\n .layer(TimeoutLayer { timeout: Duration::from_secs(30) })\n .layer(LoggingLayer)\n}\n\nfn main() {\n // Apply same stack to different services\n let service1 = production_stack().layer(GreetingService);\n let service2 = production_stack().layer(OtherService);\n \n // Each gets the same middleware applied\n // Without Layer, you'd duplicate the wrapping code\n}\n```\n\n`Layer` allows creating reusable middleware stacks.
## Direct Wrapping with State\n\n```rust\nuse tower::Service;\nuse std::sync::Arc;\nuse std::sync::atomic::{AtomicU64, Ordering};\n\nstruct Metrics {\n requests: AtomicU64,\n}\n\nstruct MetricsService<S> {\n inner: S,\n metrics: Arc<Metrics>,\n}\n\nimpl<S, Request> Service<Request> for MetricsService<S>\nwhere\n S: Service<Request>,\n{\n type Response = S::Response;\n type Error = S::Error;\n type Future = S::Future;\n\n fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {\n self.inner.poll_ready(cx)\n }\n\n fn call(&mut self, request: Request) -> Self::Future {\n self.metrics.requests.fetch_add(1, Ordering::Relaxed);\n self.inner.call(request)\n }\n}\n\n// Direct wrapping with shared state\nfn main() {\n let metrics = Arc::new(Metrics { requests: AtomicU64::new(0) });\n \n // Must create service with metrics reference\n let service = MetricsService {\n inner: GreetingService,\n metrics: Arc::clone(&metrics),\n };\n \n // State is shared via Arc\n}\n```\n\nState can be shared via `Arc` with direct wrapping.
## Layer with Configuration\n\n```rust\nuse tower::layer::Layer;\nuse std::sync::Arc;\nuse std::sync::atomic::{AtomicU64, Ordering};\n\nstruct Metrics {\n requests: AtomicU64,\n}\n\nstruct MetricsService<S> {\n inner: S,\n metrics: Arc<Metrics>,\n}\n\nstruct MetricsLayer {\n metrics: Arc<Metrics>,\n}\n\nimpl<S> Layer<S> for MetricsLayer {\n type Service = MetricsService<S>;\n\n fn layer(&self, inner: S) -> Self::Service {\n MetricsService {\n inner,\n metrics: Arc::clone(&self.metrics),\n }\n }\n}\n\nfn main() {\n let metrics = Arc::new(Metrics { requests: AtomicU64::new(0) });\n \n // Layer holds the shared state\n let metrics_layer = MetricsLayer {\n metrics: Arc::clone(&metrics),\n };\n \n // Can wrap multiple services with same metrics\n let service1 = metrics_layer.layer(GreetingService);\n let service2 = metrics_layer.layer(OtherService);\n}\n```\n\n`Layer` captures shared configuration and applies to multiple services.
## Generic Middleware with Layer\n\n```rust\nuse tower::layer::Layer;\nuse tower::Service;\n\n// Generic middleware that works with any service\nstruct MapResponseService<S, F> {\n inner: S,\n f: F,\n}\n\nimpl<S, F, Request, Response> Service<Request> for MapResponseService<S, F>\nwhere\n S: Service<Request>,\n F: FnMut(S::Response) -> Response + Clone,\n{\n type Response = Response;\n type Error = S::Error;\n type Future = MapFuture<S::Future, F>;\n\n fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {\n self.inner.poll_ready(cx)\n }\n\n fn call(&mut self, request: Request) -> Self::Future {\n MapFuture {\n future: self.inner.call(request),\n f: self.f.clone(),\n }\n }\n}\n\nstruct MapResponseLayer<F> {\n f: F,\n}\n\nimpl<S, F> Layer<S> for MapResponseLayer<F>\nwhere\n F: Clone,\n{\n type Service = MapResponseService<S, F>;\n\n fn layer(&self, inner: S) -> Self::Service {\n MapResponseService {\n inner,\n f: self.f.clone(),\n }\n }\n}\n\nfn main() {\n let transform = MapResponseLayer {\n f: |s: String| s.to_uppercase(),\n };\n \n // Works with any service returning String\n let upper = transform.layer(GreetingService);\n}\n```\n\n`Layer` enables truly generic middleware that transforms any compatible service.
## Service Creation Timing\n\n```rust\nuse tower::Service;\nuse tower::layer::Layer;\n\n// Direct wrapping: service created immediately\nfn direct_wrapping() {\n let inner = GreetingService; // Created now\n let wrapped = LoggingService { inner }; // Wrapped now\n \n // Both services exist at this point\n // No further configuration possible\n}\n\n// Layer: service created when layer() is called\nfn with_layer() {\n let layer = LoggingLayer; // Layer exists, no service yet\n \n // Can store layer, pass it around\n \n let service = layer.layer(GreetingService); // Service created here\n \n // Or apply to different services at different times\n}\n\n// This matters when services have expensive initialization\nstruct ExpensiveService {\n config: Config,\n}\n\n// Without Layer: must create all services upfront\n// With Layer: defer creation until needed\n```\n\n`Layer` defers service creation until `layer()` is called.
## Tower Service Constraint\n\n```rust\nuse tower::Service;\nuse std::future::Future;\nuse std::pin::Pin;\n\n// Services must implement Service trait\n// This constraint enables Layer to work generically\n\n// Direct wrapping requires knowing the exact service type\nfn direct_composition<S>(inner: S) -> LoggingService<S>\nwhere\n S: Service<String>,\n{\n LoggingService { inner }\n}\n\n// Layer works with Service bounds generically\nstruct GenericLayer;\n\nimpl<S> Layer<S> for GenericLayer {\n type Service = LoggingService<S>;\n\n fn layer(&self, inner: S) -> Self::Service {\n LoggingService { inner }\n }\n // No Service bound needed on S here\n // The resulting LoggingService implements Service if S does\n}\n\n// Layer can be applied to any S, even non-services\n// (though the result won't be useful)\n```\n\n`Layer` doesn't require `Service` bounds in its definition, enabling flexible composition.
## Layer for Per-Request Service Creation\n\n```rust\nuse tower::Service;\nuse tower::layer::Layer;\nuse std::task::{Context, Poll};\n\n// In server frameworks like hyper/tower, each connection gets a new service\n// Layer is essential for this pattern\n\n// MakeService: creates Service instances\n// Layer transforms the output of MakeService\n\nstruct MakeLogging<S> {\n inner: S,\n}\n\nimpl<T, S> Service<T> for MakeLogging<S>\nwhere\n S: Service<T>,\n{\n type Response = LoggingService<S::Response>;\n type Error = S::Error;\n type Future = MakeLoggingFuture<S::Future>;\n\n fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {\n self.inner.poll_ready(cx)\n }\n\n fn call(&mut self, target: T) -> Self::Future {\n MakeLoggingFuture {\n future: self.inner.call(target),\n }\n }\n}\n\n// Apply Layer to MakeService\nstruct MakeLoggingLayer;\n\nimpl<S> Layer<S> for MakeLoggingLayer {\n type Service = MakeLogging<S>;\n\n fn layer(&self, inner: S) -> Self::Service {\n MakeLogging { inner }\n }\n}\n```\n\n`Layer` enables middleware for `MakeService` that creates per-connection services.
## When Direct Wrapping Works\n\n```rust\n// Direct wrapping is fine when:\n\n// 1. Single service instance, known at compile time\nfn static_service() -> impl Service<String> {\n LoggingService { inner: GreetingService }\n}\n\n// 2. No need to apply same middleware to multiple services\nfn one_off() {\n let service = TimeoutService {\n inner: GreetingService,\n timeout: Duration::from_secs(5),\n };\n // No need to reuse this configuration\n}\n\n// 3. Simple composition without configuration\nfn simple() {\n let service = LoggingService { inner: GreetingService };\n // Middleware has no configurable parameters\n}\n\n// 4. Testing or prototyping\nfn test() {\n let service = LoggingService { inner: MockService };\n // Quick test setup, doesn't need abstraction\n}\n```\n\nDirect wrapping is simpler when you don't need reuse or configuration.
## When Layer is Essential\n\n```rust\n// Layer is essential when:\n\n// 1. Middleware needs to wrap multiple different services\nfn multi_service() {\n let layer = LoggingLayer;\n let s1 = layer.layer(ServiceA);\n let s2 = layer.layer(ServiceB);\n let s3 = layer.layer(ServiceC);\n}\n\n// 2. Middleware has configuration\nfn configured() {\n let timeout = TimeoutLayer { timeout: Duration::from_secs(5) };\n let s1 = timeout.layer(ServiceA);\n let s2 = timeout.layer(ServiceB);\n}\n\n// 3. Building reusable middleware stacks\nfn stack() {\n let stack = ServiceBuilder::new()\n .layer(TimeoutLayer { timeout: Duration::from_secs(30) })\n .layer(LoggingLayer)\n .layer(RateLimitLayer { rate: 100 });\n \n let s1 = stack.service(ServiceA);\n let s2 = stack.service(ServiceB);\n}\n\n// 4. Server frameworks that create per-connection services\nfn server() {\n // Hyper/tower-web use Layer for this pattern\n}\n```\n\n`Layer` is essential for reusable, configurable, composable middleware.
## Synthesis\n\n**Layer purpose**:\n- Factory that transforms `Service` into wrapped `Service`\n- Captures middleware configuration\n- Enables composition via `ServiceBuilder`\n- Works with service factories (`MakeService`)\n\n**Direct wrapping characteristics**:\n- Simple struct composition\n- Creates entire stack immediately\n- Configuration embedded in struct fields\n- Requires inner service to exist\n\n**Layer advantages**:\n- Reusable across different services\n- Deferred service creation\n- Declarative composition\n- Works with service factories\n- Configuration captured in layer\n\n**Direct wrapping advantages**:\n- Simpler for one-off cases\n- No trait overhead\n- Easier to understand\n- Direct control over types\n\n**Key architectural difference**:\n- Direct wrapping: \"I have this service, wrap it\"\n- Layer: \"Here's how to wrap any service\"\n\n**Use direct wrapping when**:\n- Single service instance\n- No middleware reuse needed\n- No configuration parameters\n- Quick prototypes\n\n**Use Layer when**:\n- Multiple services need same middleware\n- Middleware has configuration\n- Building middleware stacks\n- Server frameworks (per-connection services)\n- Creating reusable middleware libraries\n\n**Key insight**: `Layer` is the abstraction that makes middleware composable. Without it, each middleware would need to be manually nested as `Middleware3<Middleware2<Middleware1<Service>>>`, creating verbose, error-prone configuration. With `Layer`, you declare a stack of transformations as `ServiceBuilder::new().layer(A).layer(B).layer(C).service(inner)`, and the types are handled automatically. The `Layer` trait is what enables `tower-http` and `tower` middleware to work together: each middleware provides both a `Service` implementation (the actual behavior) and a `Layer` implementation (how to create that service from any inner service). This separation of \"what the middleware does\" from \"how to create it\" is what allows middleware to be composed without knowing about each other, and what enables server frameworks to apply middleware to service factories that create fresh instances per connection.","path":"/articles/260_tower_layer_trait_vs_wrapping.md"}}