Hi all!
So, I've been working on an implementation of the actor model, with remote actors. And it works, kind of. The problem is that the code is quite honestly a hot mess lmao.
Reading about Tower, I thought maybe I could refactor my stuff around it, with a core Service for the actors themselves, and a network Layer to read (and try to decrypt) and write (after encrypting) messages. But I couldn't find any examples of a custom transport with Tower! Only HTTP services.
Is what I want to do even practical in Tower? If so, I'm probably going the wrong way about this. Why?
use std::{
future::Future,
net::TcpStream,
pin::Pin,
task::{Context, Poll},
};
use tower::Service;
pub struct Message<T: Send>(T);
// This is the only trait the library should expose to the users
pub trait Actor<I, O, E> {
// I don't like that this returns an explicit, pinned, boxed future
// but otherwise I would have lifetime issues in call, which requires
// &self to be 'static
fn handle(&mut self, arg: I) -> Pin<Box<dyn Future<Output = Result<O, E>>>>;
}
// everything from this point onwards is internal machinery, not to be exposed to the user
impl<I, O, E> Service<I> for dyn Actor<I, O, E>
where
I: 'static,
O: 'static,
E: 'static,
{
type Response = O;
type Error = E;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: I) -> Self::Future {
let future = self.handle(req);
Box::pin(future)
}
}
struct ActorTransport<T>(T);
impl<T> Service<TcpStream> for ActorTransport<T>
where
T: Service<I>, // <- can't find type parameter, and even if I specified it,
// it wouldn't be constrained anywhere else
{
type Response = (); // what should the response even be?
type Error = (); // same with this error -- maybe if the deserialization fails?
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: TcpStream) -> Self::Future {
// 1. read a message from the stream
// 2. deserialize it
// 3. call self.0.call(...)
// 4. serialize the result and send it back
// 5? what should the response be?
todo!()
}
}