Cannot move out of `*X` which is behind a shared reference

Continuing from this thread on implementing 2-players mode for a tetris game.

When one player reaches a game over state, I want to send a message to other player to inform them and restart the game. This communication will be facilitated through a TCP stream, with data being transferred from one thread to the main thread using mpsc::channel:

player1 <--- TCP stream ---> [`receive_message` thread <--- mpsc::channel --> main thread] player 2

Let's take a look at my Game struct:

struct Game {
    stream: Option<TcpStream>,
    receiver: Option<Receiver<MessageType>>,
}

impl Game {
    fn new(
        conn: Connection,
        stream: Option<TcpStream>,
        receiver: Option<Receiver<MessageType>>,
    ) -> Self {

The receive_message thread:

            let (sender, receiver): (Sender<MessageType>, Receiver<MessageType>) = channel();
            let mut game = Game::new(conn, Some(stream), Some(receiver));

            thread::spawn(move || {
                thread_barrier_clone.wait();
                receive_message(&mut stream_clone, sender);
            });
fn receive_message(stream: &mut TcpStream, sender: Sender<MessageType>) {
    let mut buffer = [0u8; 256];
    loop {
        match stream.read(&mut buffer) {
            Ok(n) if n > 0 => {
                let msg = String::from_utf8_lossy(&buffer[0..n]);
                println!("received: {}", msg);
                if msg.starts_with(PREFIX_CLEARED_ROWS) {
                    if let Ok(rows) = msg.trim_start_matches(PREFIX_CLEARED_ROWS).parse() {
                        println!("sending cleared rows: {}", rows);
                        sender.send(MessageType::ClearedRows(rows)).unwrap();
                    }
                } else if msg.starts_with(PREFIX_NOTIFICATION) {
                    let msg = msg.trim_start_matches(PREFIX_NOTIFICATION).to_string();
                    sender.send(MessageType::Notification(msg)).unwrap();
                }
            }
            Ok(_) | Err(_) => {
                break;
            }
        }
    }
}

On the receiver side (main thread):

    fn handle_event(&mut self, stdout: &mut std::io::Stdout) -> Result<()> {
        let mut drop_timer = Instant::now();
        let mut soft_drop_timer = Instant::now();

        loop {
            if self.paused {
                self.handle_pause_event(stdout)?;
            } else {
                if let Some(receiver) = self.receiver.take() {
                    self.process_received_messages(stdout, &receiver)?;
                }
    fn process_received_messages(
        &mut self,
        stdout: &mut io::Stdout,
        receiver: &Receiver<MessageType>,
    ) -> Result<()> {
        let receiver_clone = receiver.clone();
        for message in receiver.try_iter() {
            match message {
                MessageType::Notification(msg) => match msg.as_str() {
                    GAME_OVER_MESSAGE => {
                        loop {
                            if poll(Duration::from_millis(10))? {
                                let event = read()?;
                                match event {
                                    Event::Key(KeyEvent {
                                        code,
                                        modifiers: _,
                                        kind: _,
                                        state: _,
                                    }) => match code {
                                        KeyCode::Enter | KeyCode::Char('c') => {
                                            self.render(stdout);
                                            self.paused = false;
                                            break;
                                        }
                                        KeyCode::Char('r') => {
                                            reset_game(self.stream.take(), Some(*receiver_clone))?;
                                        }

The reset_game function:

fn reset_game(stream: Option<TcpStream>, receiver: Option<Receiver<MessageType>>) -> Result<()> {
    let conn = open()?;
    let mut game = Game::new(conn, stream, receiver);

    let mut stdout = std::io::stdout();
    game.render(&mut stdout);

    match game.handle_event(&mut stdout) {
        Ok(_) => {}
        Err(err) => eprintln!("Error: {}", err),
    }

    Ok(())
}

However, there is an error:

error[E0507]: cannot move out of `*receiver_clone` which is behind a shared reference
   --> src/main.rs:583:81
    |
583 | ...                   reset_game(self.stream.take(), Some(*receiver_clone))?;
    |                                                           ^^^^^^^^^^^^^^^ move occurs because `*receiver_clone` has type `std::sync::mpsc::Receiver<MessageType>`, which does not implement the `Copy` trait

If I try to change the signature to:

    fn process_received_messages(
        &mut self,
        stdout: &mut io::Stdout,
        receiver: Receiver<MessageType>,
    ) -> Result<()> {

then the error will be:

error[E0505]: cannot move out of `receiver` because it is borrowed
   --> src/main.rs:582:81
    |
526 |         receiver: Receiver<MessageType>,
    |         -------- binding `receiver` declared here
527 |     ) -> Result<()> {
528 |         for message in receiver.try_iter() {
    |                        ------------------- borrow of `receiver` occurs here
...
582 |                                             reset_game(self.stream.take(), Some(receiver))?;
    |                                                                                 ^^^^^^^^ move out of `receiver` occurs here

How can I fix this?

You can view my code here.

Thank you.

MPSC is multiple producer, single consumer. Thus, Receiver isn't – can't be – Clone. Therefore you are only cloning the reference at the beginning of process_received_messages(), which is useless.

You'll need to redesign your code in a way that it does not require nonsensical operations such as cloning a supposedly unique receiver.

4 Likes

If you need to clone the receivers, use crossbeam channels.

@H2CO3 Thank you for your suggestion.

I solved it by not passing receiver to the reset_game:

fn reset_game(game: &mut Game, stdout: &mut io::Stdout) {
    game.reset();
    game.render(stdout);

    match game.handle_event(stdout) {
        Ok(_) => {}
        Err(err) => eprintln!("Error: {}", err),
    }
}

In the key event handling, I use a flag to indicate that a reset is needed and perform a reset later:

        let mut reset_needed = false;
        loop {
            if self.paused {
                self.handle_pause_event(stdout)?;
            } else {
                if let Some(receiver) = &self.receiver {
                    for message in receiver.try_iter() {
                        match message {
                            MessageType::Notification(msg) => {
                                loop {
                                    if poll(Duration::from_millis(10))? {
                                        let event = read()?;
                                        match event {
                                            Event::Key(KeyEvent {
                                                code,
                                                modifiers: _,
                                                kind: _,
                                                state: _,
                                            }) => match code {
                                                KeyCode::Char('r') => {
                                                    reset_needed = true;
                                                    break;
                                                }
                                                _ => {}
                                            },
                                            _ => {}
                                        }
                                    }
                                }
                            }
                        }
                    }
                }

                if reset_needed {
                    reset_game(self, stdout);
                }