Question

I can create a TcpListener and TcpAcceptor pretty easily in Rust, but I don't quite get what they do. It seems like the Listener doesn't really listen and is just a settings struct for the acceptor.

let tcp_listener = TcpListener::bind(addr);
let mut acceptor = tcp_listener.listen();
drop(acceptor);

One of the things that got me puzzled is the drop call. I've no idea where drop is defined, I've searched the standard library a bit but I can only find it as a trait method.

Also why does dropping the acceptor stop the listening? Shouldn't that only stop the accepting? Surely you'd want to drop the listener. Why is there even a TcpListener as the acceptor seems to do everything?

Was it helpful?

Solution

In short, in a BSD sockets framework here is how usually a server is started:

  • a main server socket is created using socket() system call;
  • the main socket is bound to specific address/port using bind() system call;
  • the main socket is "started" using listen() system call; from this moment until the main socket is closed it will take over the port it is bound to and will listen for incoming connections;
  • when the main socket is listening, it is possible to call accept(); this call will block until some client connects and then it will return a new socket which represents a connection with this client; all communication with this client goes through this socket; when the interaction is over, this socket should be closed.

The naming of main methods used by TCP stack in Rust follows standard BSD sockets naming, however, Rust API is more abstract, so some low-level concepts are hidden.

TcpListener::bind() creates a new main socket (socket() system call) and binds it to the provided address (bind() system call). It returns an object, TcpListener, which encapsulates main socket in "created, but not active" state.

TcpListener provides a method called TcpListener::listen() which wraps listen() system call and "activates" server socket. It returns a new object, TcpAcceptor, which provides convenient interface for accepting incoming connections. It represents main socket in "created and active" state.

TcpAcceptor in turn has several methods (TcpAcceptor::incoming() and TcpAcceptor::accept()) which wrap accept() system call and block the current task until some client establishes a connection which results in TcpStream - an object representing client socket.

All OS resources associated with all sockets are freed when their destructors are called. Destructors in Rust can be associated with any custom structure. To add a destructor to your structure you have to implement Drop trait for it. Destructor is called when an instance of such structure goes out of scope, for example:

struct Test { value: int }

impl Drop for Test {
    fn drop(&mut self) {
        println!("Dropping Test: value is {}", self.value);
    }
}

fn main() {
    let test = Test { value: 10 };
    // main ends, `test` goes out of scope
}

This program prints Dropping Test: value is 10.

What is important for structures with Drop trait implemented on them is that they aren't implicitly copyable. It means that when instances for such structures are passed to function arguments or assigned to different variables, they are moved out from their original locations, that is, you can no longer use them through their original variables. For example (using Test definition above):

let test = Test { value: 10 };
let test2 = test;
println!("{}", test.value);  // error: use of moved value: `test`

drop() function which confused you is defined in core::mem module; here is its documentation. Its implementation is very simple; it looks like this:

fn drop<T>(x: T) { }

It does nothing at all! But because it accepts its argument by value, that argument is moved into the drop() call. Then said argument immediately goes out of scope because the function returns. If T has a destructor associated with it, it will be run here. So, drop() is essentially a "black hole": everything you put into it is destroyed (unless it is implicitly copyable, that is). Very nice property of the language is that such function can be written in plain Rust, without any kind of magic.

In fact, drop() has little use because all destructors are always run when corresponding objects go out of scope. It is occasionally useful when some complex logic with destructible resources is needed, but it happens very rarely.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top