Description
I was messing around with abstractions on top of Service, and I'm not sure I understand it but it seems to me that there may be a rather deep problem with the API.
(This is basically an ellaboration of the fears I've held for a while that without impl Trait
in traits or at least ATCs, futures will not really work.)
The high level problem is this: Service::Future
is essentially required to have no relationship in lifetime to the borrow of self
in call. A definition that linked those two lifetimes would require associated type constructors.
This means that you cannot borrow self
at any asynchronous point during the service, only while constructing the future. This seems bad!
Consider this simple service combinator, which chains two services together:
struct ServiceChain<S1, S2> {
first: S1,
second: S2,
}
impl<S1, S2> Service for ServiceChain<S1, S2>
where
S1: Service + Sync + 'static,
S2: Service<Request = S1::Response, Error = S1::Error> + Sync + 'static,
S1::Future: Send,
S2::Future: Send,
{
type Request = S1::Request;
type Response = S2::Response;
type Error = S1::Error;
type Future = futures::future::BoxFuture<Self::Response, S1::Error>;
fn call(&self, request: Self::Request) -> Self::Future {
self.first.call(request)
.and_then(move |intermediate| self.second.call(intermediate))
.boxed()
}
}
Is it intentional that Service's future type is defined so that it could outlive the service being borrowed? Is a service supposed to have a method that constructs the type needed to process the request (separately for each request) and passes it into the future?
Chaining futures and streams can suffer from a sort of related problem which I was forced to solved with reference counting.