Making a relayed connection for two peers via relay server

I'm trying to build simple relay server that can connect 2 peers with each-other. One client is a "server" application behind the NAT and another is acting like "client" also behind NAT.
So i took an example from tokio's proxy tutorial and slightly modify it - I added a HashMap where I store TcpStream and id of this stream. When I connect to my relay server with first client, who'll act as listener, I add his id and TcpStream to a HashMap. Then get that TcpStream from a HashMap when second client irrives.

use tokio::io;                                                                                                                                                                                                                               
use tokio::net::TcpListener;
use tokio::net::TcpStream;
use tokio::select;
use std::collections::HashMap;

pub const LISTEN: u8 = 0;
pub const CLIENT: u8 = 1;

async fn proxy() -> io::Result<()> {
    let listener = TcpListener::bind("".to_string()).await?;
    let mut sh_streams = HashMap::new();
    loop {
        let (stream, _) = listener.accept().await?;

        let mut buf = [0u8;2];
        match stream.try_read(&mut buf) {
            Ok(n) => {
                let id = buf[1];
                match buf[0] {
                    LISTEN => {
                        sh_streams.insert(id, stream);
                    CLIENT => {
                        let listener = sh_streams.get_mut(&id).unwrap();
                        tokio::spawn(async move {
                            let (mut read, mut write) = stream.into_split();
                            let (mut read2, mut write2) = listener.into_split();
                            tokio::select! {
                                _=io::copy(&mut read,&mut write2)=>{},
                                _=io::copy(&mut read2, &mut write)=>{}
                    _ => {}
            Err(e) => {
                return Err(e.into());

async fn main() -> io::Result<()> {


It compiles with error -

    --> src/
29   | ...                   let (mut read2, mut write2) = listener.into_split();
     |                                                     ^^^^^^^^^------------
     |                                                     |        |
     |                                                     |        `*listener` moved due to this method call
     |                                                     move occurs because `*listener` has type `tokio::net::TcpStream`, which does not implement the `Copy` trait

I'm new to rust, so maybe I'm missing something... Please help!

Getting a mutable reference to your listener here has two problems. The fist one is the error you currently see, i.e. we'd move out of the reference when we call listener.into_split(). into_split consumes the TcpStream[1] and returns an owned reading and writing half. Even if we were to fix it by using listener.split(), which does not consume TcpStream[2] by returning references to the reading and writing halves, we'd face the second problem. Namely that you can't pass a reference into the async block executed by tokio::spawn, which needs the future it executes to fulfil the 'static bound. So passing &mut TcpStream into the task will not work.

IMO, instead of getting a mutable reference to the listener stored in your hash map, it'd make more sense to remove it from the hash map, taking full ownership here. Replacing get_mut(&id) with remove(&id) will give you the owned TcpStream, which you can call into_slit() on and pass into the spawned task.

Here a link to the playground:

  1. which we can see from the self argument of into_split ↩︎

  2. which we can see from the &mut self argument of split ↩︎

remove() was the magic word!
Thank you!