Just An Application

July 26, 2014

Rust Revisted: Ch…ch…ch…changes Or No More Sockets Anymore

Looking at Swift and some of its more Rust-like features, e.g., tuples, reminded me that its been over a year since I last looked at Rust.

In that time its gone from version 0.07 to 0.11 and there have been lots of changes including the disappearance of all the socket functions.

In their place we have the structs

  • TcpListener

  • TcpAcceptor

  • TcpStream

These are all in the std::io::net::tcp module.

Listening For Connections

To listen for incoming connections you call the static method

    TcpListener::bind

passing it an address and a port, like so (this and the other code snippets are all from examples given in the documentation)

    ...

    let listener = TcpListener::bind("127.0.0.1", 80);

    ...

This creates a TcpListener instance and binds it to the given address and port.

You can then listen for incoming connections by calling the listen method

    ...
    
    let mut acceptor = listener.listen();

    ...

This creates a TcpAcceptor instance which can be used to accept incoming connections

Accepting Connections

Once you have a TcpAcceptor you can accept a single connection by calling the method accept.

Alternatively, you can call the method incoming which returns an iterator which can be used to loop over incoming connections as they are made.

    ...
    
    for stream in acceptor.incoming() {
        match stream {
            Err(e) => { /* connection failed */ }
            Ok(stream) => spawn(proc() {
                // connection succeeded
                handle_client(stream)
            })
        }
    }

    ...

The IoResult<T> Type And IO Traits

There is one thing about the example given in the documentation which is at first glance rather puzzling.

The method

    TcpListener::bind

is declared to return

    IOResult<TcpListener>

The type IoResult is declared as

    type IoResult<T> = Result<T, IoError>

and

    Result<T, E>

is a variant enum.

So the method TcpListener::bind actually returns an enum and yet in the example the method listen is called directly on the return value, i.e. the enum.

The same is true for the method listen which is defined to return

    IOResult<TcpAcceptor>

So why does the code even compile ?

The answer is that IoResult implements all the methods defined for the traits

  • Acceptor
  • Listener
  • Reader
  • Seek
  • Writer

For example, this is the definition of the IOResult implementation of the Listener trait

    impl<T, A: Acceptor<T>, L: Listener<T, A>> Listener<T, A> for IoResult<L> {
        fn listen(self) -> IoResult<A> {
            match self {
                Ok(listener) => listener.listen(),
                Err(e) => Err(e),
            }
        }
    }

which can be found in the file

    $(RUST_SRC)/src/libstd/io/result.rs

As you can see if the IOResult contains a Listener the result of calling the method listen on it is returned, otherwise the IOResult must contain an error and that is returned.

Whilest not having to explicitly unwrap the result of each method which returns an IOResult is certainly convenient there can be a potential drawback in some cases.

If you run the following, which is based on the example code from the documentation, under Mac OS X

    fn main()
    {
        // bind the listener to the specified address
        
        let listener = TcpListener::bind("127.0.0.1", 80);
    
        match listener
        {
            Ok(_) =>
            {
                println!("bind: have listener");
            }
            Err(_) =>
            {
                println!("bind: have error");
            }
        }
    
        let mut acceptor = listener.listen();
    
        match acceptor
        {
            Ok(_) =>
            {
                println!("listen: have acceptor");
            }
            Err(e) =>
            {
                println!("listen: have error");
            }
        }
    
    
        // accept connections and process them, spawning a new tasks for each one
    
        for stream in acceptor.incoming()
        {
            match stream
            {
                Err(e) =>
                {
                    /* connection failed */
                    println!("incoming: have error {}", e);
                }
    
                Ok(stream) => spawn(proc()
                                    {
                                       // connection succeeded
                                       handle_client(stream)
                                    })
            }
        }
    
        // close the socket server
        drop(acceptor);
    }

you get

    bind: have error
    listen: have error

because by default the program cannot open port 80, followed by

    incoming: have error permission denied (Permission denied)

repeated ad infinitum, or at least until the point that you hit Ctrl-C, as the program screams around in a tight loop doing absolutely nothing.

In non-example code the failure case within the loop would probably cause the loop to exit or the task to terminate so it wouldn’t loop indefinitely but even so it would probably be better to check whether the initial call to bind actually worked before doing anything else.

Explicit Error Checking

The problem with explicit error checking is that it can get quite messy quite fast.

For example

    fn main()
    {
        match TcpListener::bind("127.0.0.1", 80)
        {
            Ok(listener) =>
            {
                println!("bind: have listener");
                match listener.listen()
                {
                    Ok(mut acceptor) =>
                    {
                        println!("listen: have acceptor");
                        for stream in acceptor.incoming()
                        {
                            match stream
                            {
                                Err(e) =>
                                {
                                    /* connection failed */
                                    println!("incoming: have error");
                                }

                                Ok(stream) => spawn(proc()
                                                    {
                                                        // connection succeeded
                                                        handle_client(stream)
                                                    })
                            }
                        }
                        // close the socket server
                        drop(acceptor);
                    }
                    Err(_) =>
                    {
                        println!("listen: have error");
                    }
                }
            }
            Err(_) =>
            {
                println!("bind: have error");
            }
        }
    }

Whilest not completely impenetrable its not the kind of code you would want to have to maintain if you could possibly avoid it.

The try Macro

There is a utility macro

    try

which is intended to help in this situation.

It is defined like this

    macro_rules! try(
        ($e:expr) => (match $e { Ok(e) => e, Err(e) => return Err(e) })
    )

The definition can be found in the file

    $(RUST_SRC)/rust-0.11.0/src/libcore/macros.rs

As you can see, it works by returning from the function immediately if an error occurs, so you can do something like this

    ...
    
    let listener = try!(TcpListener::bind("127.0.0.1", 80));
    
    ...

and if the call to TcpListener::bind fails the calling function returns immediately

We can then do the same thing for the call to listen

    ...
    
    let mut acceptor = try!(listener.listen());
    
    ...

so we end up with something like this

    fn run(host:&str, port: u16) -> IoResult<????>
    {
        // bind the listener to the specified address
    
        let listener = try!(TcpListener::bind(host, port));
    
        println!("listen: have listener");
    
        let mut acceptor = try!(listener.listen());
    
        println!("listen: have acceptor");
    
        // accept connections and process them, spawning a new tasks for each one
    
        for stream in acceptor.incoming()
        {
            match stream
            {
                Err(e) =>
                {
                     /* connection failed */
                    println!("incoming: have error");
                }
    
                Ok(stream) => spawn(proc()
                                    {
                                        // connection succeeded
                                        handle_client(stream)
                                    })
            }
        }
        // close the socket server
        drop(acceptor);
        return ????;
    }

Note that the function has to have a non-void return type and in this case it has to be

    IoResult<????>

or

    Result<????, IoError>

if you prefer, where ???? is the type of whatever the function returns in the non-error case.

Since there really isn’t anything to return from the function unless something goes wrong the easiest thing to do is reflect that in the return type and declare it to be

    IoResult<()>

i.e., the empty tuple type, so we now have

    fn run(host:&str, port: u16) -> IoResult<()>
    {
        // bind the listener to the specified address
    
        let listener = try!(TcpListener::bind(host, port));
    
        println!("listen: have listener");
    
        let mut acceptor = try!(listener.listen());
    
        println!("listen: have acceptor");
    
        // accept connections and process them, spawning a new tasks for each one
    
        for stream in acceptor.incoming()
        {
            match stream
            {
                Err(e) =>
                {
                    /* connection failed */
                    println!("incoming: have error");
                }
    
                Ok(stream) => spawn(proc()
                                    {
                                        // connection succeeded
                                        handle_client(stream)
                                    })
            }
        }
    
        // close the socket server
        drop(acceptor);
        return ();
    }

which is definitely an improvement upon the deeply nested version above.


Copyright (c) 2014 By Simon Lewis. All Rights Reserved.

Unauthorized use and/or duplication of this material without express and written permission from this blog’s author and owner Simon Lewis is strictly prohibited.

Excerpts and links may be used, provided that full and clear credit is given to Simon Lewis and justanapplication.wordpress.com with appropriate and specific direction to the original content.

Create a free website or blog at WordPress.com.

%d bloggers like this: