#Help defining types for rpc

19 messages · Page 1 of 1 (latest)

thorny haven
#

Hey, I'm doing a lot of rpc/socket/protocol things and I keep hitting the same problem every time and I feel like there should be a better solution. So I was wondering if anyone here would have a suggestion or even just a link to a lib so it can help me think outside the box

the idea is, I always end up with an enum like this

// all the actions the receiver can answer to
enum Message {
  SetA(A),
  GetA,
}

enum Response {
  Unit, // the action doesn't return anything
  TheA(A), // the response that `GetA` returns
}

The problem here is that although this makes the receiver code quite simple, just match on the message enum and dispatch
It makes the "caller's" job quite awkward

fn get_a() -> A {
  match SOME_TRANSPORT.send(Message::GetA) {
    Response::TheA(a) => a,
    x => panic!("Unexpected response: {x:?}"),
  }
}

So my question is, is there a way to create an association between "question types" and "response types" in the type system so that I don't have to runtime check? I have experimented with traits and associated types but it makes the receiver code quite messy

trait Message {
  type Response;
}
struct SetA(A);
impl Message for SetA {
  type Response = ();
}
keen tree
#

Hey

#

obviously I don't know about your packet format so I improvised a mini protocol but that's just as an example

thorny haven
#

hmm

#

that still has the same matching on things and panicking on incorrect response

#

my real message enum e more like this

#[derive(Debug, Serialize, Deserialize)]
enum MessageKind {
    Create { items: Vec<Item>, with_video: bool },
    // actions
    Pause,
    QueueClear,
    LoadFile { item: Item },
    LoadList { path: PathBuf },
    QueueMove { from: usize, to: usize },
    QueueRemove { index: usize },
    QueueLoop { start_looping: bool },
    QueueShuffle,
    // getters
    ChapterMetadata,
    Filename,
    IsPaused,
    MediaTitle,
    PercentPosition,
    Queue,
    QueueFilename,
    QueueIsLooping,
    QueuePos,
    QueueSize,
    Volume,
    QueueNFilename { index: usize },
    QueueN { index: usize },
}
tawny forge
#

if it wasn't rpc I'd suggest sending a return channel for the receiver to write the response to

thorny haven
#

and response is this

#[derive(Debug, Serialize, Deserialize)]
enum Response {
    Create(usize),
    Metadata(Metadata),
    Bool(bool),
    Text(String),
    Real(f64),
    Item(QueueItem),
    Items(Vec<QueueItem>),
    Integer(usize),
    Unit,
}
thorny haven
#

serialization and types are not great friends sometimes haha

tawny forge
#

can you elaborate on what you tried with the traits and associated types?

thorny haven
# tawny forge can you elaborate on what you tried with the traits and associated types?
trait Message {
  type Response;
}

fn send<M>(m: M) -> M::Response 
where M: Message + Serialize,
      M::Response: Deserialize
{
  let payload = serde_json::to_vec(&m).unwrap();
  socket.send(&payload);
  let line = socket.read_line();
  serde_json::from_slice(&line).unwrap();
}

basically something like this, it's trivial to implement by the "caller", just one method does all things, but the receiver can't just match on every message type

tawny forge
#

are caller and receiver separate applications?

#

because if that trait had a respond method then it could work

thorny haven
#

it's all written in the same crate

#

hmm

#

but how do you deserialize?

#
struct Pause;
impl Message for Pause {
  type Output = ();
}

struct CurrentIndex;
impl Message for CurrentIndex {
  type Output = usize;
}

fn handle_message(socket: Socket) {
  let line = socket.read_line();
  let msg = from_slice::<???>(&line);
  let response = msg.respond();
  socket.send(&to_vec(&response));
}