Since coming back to Rust, it seems Tokio has undergone an overhaul. I didn't know how to use it to begin with, but this new API seems to be scarce on tutorials. I looked at the TCP echo server example in the 'getting started' section, and while I understood it, I can't seem to wrap my mind around how to support multiple connections. I believe what I'm trying to figure out is how would I make an asynchronous server which mutably borrows TCP streams one at a time? I mean, obviously, I want the echo server to be able to echo one connection's input back to them as well as others, but trying to work that in gives me moved binding errors.
I'm not sure if this will help - it's a pretty high-level question, but I'll have a go.
In an async environment you should try to think of your server in terms of tasks, which are independent processes created and destroyed over time. Sometimes tasks are executing code but most of the time they're dormant, waiting for some specific sort of input. For example: new data from a particular TCP connection, or for a timer to expire.
While you can use Arc and Mutex to share state between tasks, it feels most natural when the required resources are owned by the relevant task, by moving it (in the ownership sense:
async move | ... |) into the relevant closure.
Consider a basic webserver. It begins with one task, which has the listening socket as its input. Each time it accepts a new client, it spawns a new task to handle it. Those new spawned tasks wait on their own inputs - usually, waiting for new data from the remote client. Here, each socket is owned by its own separate spawned task. They all operate independently.
For an example of an server that works in exactly this way see
echo.rs from the tokio examples: https://github.com/tokio-rs/tokio/blob/master/examples/echo.rs
You are asking about being able to echo back to other streams as well. Compare the above with this example,
The key pattern is that the application is adding its own channels to create communication between the individual tasks. When it does a broadcast, it doesn't have a collection of
TcpStreams that it borrows in turn - it has a collection of channel Senders. In due course, every Receiver will get the message, waking up the relevant task that owns the relevant
TcpStream. In principle you could instead have a task that owns all the
TcpStreams, or all the write halves, but it would be worth seeing if the
chat.rs approach fits your needs first.