I've been trying to get my feet wet with futures in rust, and followed various online tutorials and examples. Starting from a pretty typical "chat server" example, I ended up with code like this.
Most of the examples make sense as they are structured and they are illustrating a basic skeleton, but as I try to move code out into libraries/out of a single main function, I wonder if I'm using the proper abstractions at the boundaries. This code is still a toy example, but I am trying to understand how to evolve simple implementations into bigger projects. I have looked at the tokio chat server, as well as other similar examples.
The particular set up here is a chat server where clients connect via websockets. I then introduced concepts such as:
-
Hub
which is effectively a registry of connectedClient
s, although includes things such as an internalInterval
Stream
which sends heart beats, etc. -
Client
which represent each websocket connection.
Within the hub
module (what one might pull out to a library if this were more than a toy example), I wanted to avoid directly relying on tokio
, so code that originally might have directly called tokio::spawn
I wanted to instead have return Future
or Stream
objects, so the main
fn could handle dispatching them onto the tokio
runtime instead.
But code like here feels somewhat forced, and took some poking and prodding to land on. Effectively I am mapping an incoming Stream
into a Stream
of Future
s, and passing that Stream
back out to main
, where it can be spawn
ed.
In another case, for this Drop
impl, to avoid a spawn
I am instead doing wait()
.
Ultimately, I wonder if I'm following intended usage for Future
s. Or am I shoehorning in the wrong patterns/concepts? For example, the "consume Stream
, produce Stream
of Future
s" pattern was somewhat motivated in my mind as analogous to iterating over a collection and flat_map
ping.
- Is returning
impl Stream
andimpl Future
the appropriate abstraction at a library boundary (where I don't want to e.g.tokio::spawn
internally to the library)? - Is it ok to simply
wait()
in cases such as aDrop
impl? - Would a more ergonomic approach be to implement
Stream
orFuture
for types such asClient
orHub
?
I don't know if I should be comfortable using spawn
more liberally/this sort of design ends up placing too much work within a task and reducing the potential of using Future
s. The hub_loop
definition in particular I am concerned about; by trying to pipeline both the consumer and producer aspects, am I introducing unnecessary serialization or something similar?