-
Notifications
You must be signed in to change notification settings - Fork 13.6k
Description
I am implementing a service architecture, where each service can be thought of as middleware. It makes sense to define complex services which need to manage their own complex state as their own structs and implement my Service
trait for each one. However, for simple operations, e.g. logging or manipulating some argument in place, it makes more sense to have a wrapper service which receives the previous service's "request", does whatever it needs to do, and passes it on. The wrapper service also implements the Service
trait.
The problem here is that there is an exponential type length complexity blowup when passing in the function.
Probably related: #54540, note how there's no closure in my example
MVCE below: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=17d88bb1e552b2cc285ced44777e726b
(complex services: BaseService / MyService, stateless services: logger, doubler)
// The objective is to be able to wrap existing services with simple functions, without
// having to define a new service each time we want to perform a very minimal action
// on the data flow. We could define a LoggerService and a DoublingService, but that
// is _a lot_ of boilerplate.
fn main() {
let s = BaseService;
// We can create as many new services as we want,
// the type length increases linearly, as expected
let s = MyService::new(s);
let s = MyService::new(s);
// The type length of `s` doubles
let s = WrappedService::new(s, stateless_log);
// if you try to wrap a few times, your code hits the type length limit
let s = WrappedService::new(s, stateless_double);
let s = WrappedService::new(s, stateless_double);
let s = WrappedService::new(s, stateless_double);
let s = WrappedService::new(s, stateless_double);
let s = WrappedService::new(s, stateless_double);
let s = WrappedService::new(s, stateless_double);
let s = WrappedService::new(s, stateless_double);
let s = WrappedService::new(s, stateless_double);
let s = WrappedService::new(s, stateless_double)
}
type Request = u64;
type MyResult = Result<(), ()>;
pub trait Service {
fn handle(&mut self, request: Request) -> MyResult;
}
// A simple service which we we use as our base
#[derive(Clone)]
pub struct BaseService;
impl Service for BaseService {
fn handle(&mut self, _request: Request) -> MyResult {
Ok(())
}
}
// A service that takes another service (and could have more state)
#[derive(Clone)]
pub struct MyService<O> {
next: O,
}
impl<O> MyService<O>
where
O: Service + Clone + Send,
{
pub fn new(next: O) -> Self {
MyService { next }
}
}
impl<O> Service for MyService<O>
where
O: Service + Clone + Send,
{
fn handle(&mut self, request: Request) -> MyResult {
self.next.handle(request)
}
}
/// =========== Stateless Services =========
use std::sync::Arc;
// We have to store the service onion so far, and the function we want to inject
#[derive(Clone)]
pub struct WrappedService<F, I> {
f: F,
inner: Arc<I>,
}
impl<F, S> WrappedService<F, S>
where
F: Fn(S, Request) -> MyResult,
S: Service + Clone,
{
pub fn new(inner: S, f: F) -> Self {
WrappedService {
f,
inner: Arc::new(inner),
}
}
}
impl<F, S> Service for WrappedService<F, S>
where
F: Fn(S, Request) -> MyResult,
S: Service + Clone,
{
fn handle(&mut self, request: Request) -> MyResult {
(self.f)((*self.inner).clone(), request)
}
}
// Is there a way to convert these to a service, without having to go through a lot of boilerplate?
pub fn stateless_log<O: Service + Clone>(mut next: O, request: Request) -> MyResult {
println!("sending request");
let result = next.handle(request);
println!("got result");
result
}
pub fn stateless_double<O: Service + Clone>(mut next: O, request: Request) -> MyResult {
let request = 2 * request;
let result = next.handle(request);
result
}