I am trying to implement a request-response abstraction between two processes that can only exchange bytes with each other. I am aiming to have an API similar to the following:
#[derive(Serialize, Deserialize, Debug)]
struct TestReq {
value: u32,
}
#[derive(Serialize, Deserialize, Debug)]
struct TestResp {
value: u32
}
let remote = Remote::default();
let request = TestReq { value: 41 };
// response should be inferred to be of type TestResp
let response = remote.request(request).await;
assert!(response.value, 42)
So far I have defined the following traits and the definition of Remote
as follows:
trait Request: Serialize + DeserializeOwned {
type Response: Serialize + DeserializeOwned;
}
impl Request for TestReq {
type Response = TestResp;
}
#[derive(Default)]
struct Remote (VecU8SendingInterface);
impl Remote {
async fn request<R: Request>(&self, request: R) -> R::Response {
let req_data = bincode::serialize(&request).unwrap();
let resp_data = self.0.send(req_data).await;
bincode::deserialize(resp_data).unwrap()
}
}
This seems good but it has a fundamental problem. The handling function on the other side has no type information to deserialize the bytes into the original request.
async fn handle_requests(recv: VecU8ReceivingInterface) {
while let Some(req_data) = recv.next().await {
// how to specify the type here
let request = bincode::deserialize(&req_data).unwrap();
}
}
As far as I can tell, the only way to solve this problem is to wrap all my requests in an enum, however, this is making the process of adding request-response pairs to my system somewhat verbose since in addition to defining struct ReqTest
and struct RespTest
, I also need to declare this for every pair:
impl Request for TestReq {
type Response = TestResp;
}
and to manually maintain an enum with all the variants inside of it:
enum Requests {
T1(TestReq),
T2(TestReq2),
...
}
I was thinking of cleaning this up a bit by providing a proc macro that would allow one to write:
#[remote::request(response = TestResp)]
struct TestReq {
value: u32,
}
struct TestResp {
valuep1: u32
}
But I don't think I will be able to generate the Requests
enum in that way since proc macros cannot hold state between invocations. I suppose this problem has been solved several times before, but I am not sure which crates I can look at for inspiration.