How to move borrowed content to close connection

397 views Asked by At

The following code should accept tcp connection, read from them, and close them on hangup.

extern crate mio;
use mio::{EventLoop,Token,ReadHint};
use mio::tcp::{TcpListener, TcpStream};
use std::io::Read;

const L_CLIENT: Token = Token(0);
const C_CLIENT: Token = Token(1);

fn sa(port:u16) -> std::net::SocketAddr {
  let ipa : std::net::Ipv4Addr = std::net::Ipv4Addr::new(127,0,0,1);
  std::net::SocketAddr::V4(std::net::SocketAddrV4::new(ipa,port))
}

fn main(){
  let mut event_loop = EventLoop::new().unwrap();

  let lclient = TcpListener::bind(&sa(4000)).unwrap();
  event_loop.register(&lclient, L_CLIENT).unwrap();

  println!("running loop...");
  event_loop.run(&mut MyHandler{lclient:lclient,cclient:None}).unwrap();
  println!("done.");
}

struct MyHandler{
  lclient:TcpListener,
  cclient:Option<TcpStream>,
}

impl mio::Handler for MyHandler {
  type Timeout = ();
  type Message = ();

  fn readable(&mut self, event_loop: &mut EventLoop<Self>, token: Token, hint: ReadHint){
    match token {
      L_CLIENT => {
        let s=self.lclient.accept().unwrap().expect("no client??");
        match self.cclient{
          None => {event_loop.register(&s,C_CLIENT);self.cclient=Some(s);}
          Some(_) =>{println!("ignore second client");} // s should be closed as it goes out of scope
        }
      },
      C_CLIENT => {
        let mut client=self.cclient.expect("connection is gone"); // what's the problem here?
        if hint.is_hup() {
          event_loop.deregister(&client);
          self.cclient=None; // how to close connection? 
        } else  {
          let mut buf: [u8; 500] = [0; 500];
          let l=client.read(&mut buf).unwrap();
          println!("read {} bytes.",l);
        }
      },
      _ =>{println!("other Token");}
    }
  }
}

This almost works. In the C_CLIENT case I want to handle a client hangup. In that case I guess I need to move ownership for the connection from my handler to the method in order to let it go out of scope to close it.

Unfortunately rust will not allow me to move the connection:

 error: cannot move out of borrowed content

As far as I understand the problem is that &self is borrowed in the readable method and therefore is not able to access its fields.

How do I access the connection? (avoiding the above error.)

How do I close that connection? (i.e. transfer ownership.)

1

There are 1 answers

3
mbrubeck On BEST ANSWER

I assume the error in the original code is from this line:

let mut client=self.cclient.expect("connection is gone");

This isn't allowed because Option::expect takes ownership of its argument, but readable only has a borrowed reference. Only an owner can move a value to a new owner.

You could solve this by using Option:::take. Instead of moving the original option, this just borrows and mutates it, changing it to None and returning its original value:

let client = self.cclient.take().expect("connection is gone");
// Now `client` is a TcpStream and `self.cclient` is None.

However, it's not necessary to move the value at all. You already have a mutable reference, so you can just mutate it in place:

C_CLIENT => {
    if hint.is_hup() {
        event_loop.deregister(&self.cclient);
        self.cclient = None;
    } else  {
        let mut buf: [u8; 500] = [0; 500];
        let l = self.cclient.read(&mut buf).unwrap();
        println!("read {} bytes.",l);
    }
},

When you write self.cclient = None;, the old value of self.cclient is dropped immediately, closing the connection. In Rust, a value is dropped when it is no longer owned. This can happen when its owner goes out of scope or when its owner is reassigned. The Rust compiler statically inserts drop calls when a variable goes out of scope or is reassigned.