-
Notifications
You must be signed in to change notification settings - Fork 317
Open
Description
I think now that return type notation (RTN), which allows using async functions in public traits, is making decent progress (see rust-lang/rust#138424 and rust-lang/rust#109417) we should wait for that to be ready and then move to the ultimate and final form of the Service trait in one swoop. We should agree on what it would look like here so that we are ready for when the time comes.
There are two main contenders at the moment: Permits vs Tokens. Both can be blanket implemented for a simpler async call(request: Request) -> Result<Response, Error> trait.
use std::{convert::Infallible, marker::PhantomData};
mod concurrency_limit_token;
mod concurrency_limit_permit;
// a generalization of service which allow back-propagation
pub trait TokenService<Request> {
type Response;
type Error;
type Token;
// separate error types allow more expressiveness
type ReadyError;
type UnreadyError;
// arm the service
async fn ready(&mut self) -> Result<Self::Token, Self::ReadyError>;
// disarm the service
async fn unready(&mut self, token: Self::Token) -> Result<(), (Self::Token, Self::UnreadyError)>;
// can't call it call() again otherwise you'd have ambiguity with Service::call()
async fn call_with_token(
&mut self,
request: Request,
token: Self::Token,
) -> Result<Self::Response, Self::Error>;
}
pub trait PermitService<Request> {
type Response;
type Error;
type PermitError;
type Permit<'a>: Permit<Request, Response = Self::Response, Error = Self::Error>
where
Self: 'a;
async fn ready(&self) -> Result<Self::Permit<'_>, Self::PermitError>;
}
pub trait Permit<Request> {
type Response;
type Error;
async fn call(self, request: Request) -> Result<Self::Response, Self::Error>;
}
pub trait SimpleService<Request> {
type Response;
type Error;
async fn call(&self, request: Request) -> Result<Self::Response, Self::Error>;
}
impl<Request, S> PermitService<Request> for S
where
S: SimpleService<Request>,
{
type Response = S::Response;
type Error = S::Error;
type PermitError = Infallible;
type Permit<'a>
= SimplePermit<'a, Self, Self::Response, Self::Error>
where
Self: 'a;
async fn ready<'a>(&'a self) -> Result<Self::Permit<'a>, Self::PermitError> {
Ok(SimplePermit {
simple_service: self,
response: PhantomData,
error: PhantomData,
})
}
}
pub struct SimplePermit<'a, S, Response, Error> {
simple_service: &'a S,
response: PhantomData<Response>,
error: PhantomData<Error>,
}
impl<'a, Request, S, Response, Error> Permit<Request> for SimplePermit<'a, S, Response, Error>
where
S: SimpleService<Request, Response = Response, Error = Error>,
{
type Response = Response;
type Error = Error;
async fn call(self, request: Request) -> Result<Self::Response, Self::Error> {
self.simple_service.call(request).await
}
}
pub struct SimpleToken;
impl<S, Request> TokenService<Request> for S
where
S: SimpleService<Request>,
{
type Response = S::Response;
type Error = S::Error;
type Token = SimpleToken;
type ReadyError = Infallible;
type UnreadyError = Infallible;
async fn ready(&mut self) -> Result<Self::Token, Self::ReadyError> {
Ok(SimpleToken)
}
async fn unready(&mut self, _token: Self::Token) -> Result<(), (Self::Token, Self::UnreadyError)> {
Ok(())
}
async fn call_with_token(
&mut self,
request: Request,
_token: Self::Token,
) -> Result<Self::Response, Self::Error> {
self.call(request).await
}
}
mod tests {
use super::*;
struct TestSimpleService;
impl SimpleService<u8> for TestSimpleService {
type Response = u8;
type Error = Infallible;
async fn call(&self, request: u8) -> Result<Self::Response, Self::Error> {
Ok(request * 2)
}
}
async fn tests() {
let mut service = TestSimpleService;
assert_eq!(
<TestSimpleService as PermitService::<u8>>::ready(&service)
.await
.unwrap()
.call(2)
.await
.unwrap(),
4
);
let token = <TestSimpleService as TokenService<u8>>::ready(&mut service)
.await
.unwrap();
assert_eq!(service.call_with_token(2, token).await.unwrap(), 4);
}
}Unresolved design questions:
- Is it worth having a different associated for each error type or is that unnecessary complexity. (Personally, I like it for the added expressiveness)
-
&mut selfvs&selfin trait methods. - Do we need the
unready()method for disarming or should we rely on the dropping ofTokenService::Tokenfor disarmament?
Related issues:
timmcleanbyte-sourcerer and Iizuki
Metadata
Metadata
Assignees
Labels
No labels