2

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.)

michas
  • 25,361
  • 15
  • 76
  • 121
  • By the way, it's helpful if you paste the full error message, including the line number and column where it occurred. – mbrubeck Jun 11 '15 at 16:18

1 Answers1

2

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.

mbrubeck
  • 564
  • 3
  • 7
  • No, the code is not already working. - It will not compile because of the access to `self.cclient`. - How can assigning `None` close the connection? This would be completely logical in a garbage collected language, but as far as I understand in Rust closing is done at compile time iff the owning variable goes out of scope. - Simply assigning a variable will not let anything go out of scope, right? – michas Jun 11 '15 at 07:40
  • Ok, inserting a `.take()` indeed seems to be a solution. However I still have no clue what exactly the problem is and why `.take()` solves that. – michas Jun 11 '15 at 08:32
  • A value is dropped not only when its owner goes out of scope, but also when its owner receives a new value. Demonstration: http://is.gd/xeSnoI – mbrubeck Jun 11 '15 at 16:14