Posted on by & filed under Content - Highlights and Reviews, Programming & Development.

codeA guest post by Jeffery Olson, a software engineer currently living and working in the Seattle area. He’s still searching for that sweet spot between pragmatism and idealism. Besides contributions within the Rust community, he is interested in building performant HTML5 applications using emerging web technologies like WebGL and WebRTC. He can be reached at @olsonjeffery.

Computers surround us. While some devices are useful on their own, many are unable to function unless networked to other, remote systems. It just so happens that Rust exposes a set of a safe networking APIs with uniform semantics. While my previous post in this series focused on file I/O, this post will explore a small part of those networking APIs, providing code for a primitive server that responds to requests from a web browser, as a simple demonstration that’s easy to test. As a further complement to I/O, Rust’s serialization API will be introduced. These two things together help programmers compose robust, networked applications and services that communicate with structured data.

This post assumes a basic acquaintance with Rust and its semantics. The Rust Tutorial is a good place to start if you’d like to get up to speed. The code in this example is written for the 0.9 release of Rust’s compiler and standard library from early January, 2014.

A Simple TCP Server

The code below shows a simple server that binds, listens for data, immediately writes a response and then terminates. The server avoids parsing the request and what is written back is a bare bones, valid HTTP response.

The topmost block imports required functionality from the standard library, followed by the main() function. First, a resp_bytes value is created with the help of the bytes! macro (taking a static string and converting it to a byte vector). Next, a SocketAddr value is created for, port 8080 (stored in addr). The SocketAddr value is created using the from_str utility function (since it implements the FromStr trait, the compiler is able to work out the details). The addr is then used to initialize a TcpListener.

In the previous two calls, expect is invoked because they both return Option<T> values. expect is a convenience method used when receiving Option<T> values where it’s preferable to fail if the value is None (you expect a Some<T>).

In this example, the application exits after a single request is processed. A real application would probably enter a loop at the point of accept to handle incoming traffic. The TCP stream connecting the server to the client is initialized with the call to accept (again, expect comes in handy).

With the stream initialized, a mutable byte buffer, buf, initialized to zeros, is created on the stack to contain data from the client. The length of the buffer (or an explicit slice pointing at a section of it) represents the maximum amount of data a call to read will pull in. Immediately after writing, the resp_bytes value constructed above is sent back to the client with the call to write. The scope then ends by returning a string, parsed from the bytes read into buf. from_utf8 can detect the end of the string and doesn’t require a length argument. The stream‘s destructor will be invoked at the end of this scope, closing the TCP connection with the client.

The program ends with printing the returned req string to stdout. Leaving main() will result in the acceptor value being destroyed and its underlying binding to, port 8080 ending.

Handling Errors In I/O With Conditions

The above example, while succinct, leaves out a key aspect of safe applications: error handling. As of Rust 0.9, the I/O portion of Rust’s standard library manages error handling via Conditions. But in master, conditions have been removed. There is a pull request open that will set the stage for replacing them with a Result<TOk, TErr>-based scheme, partnered with language/lint support for cataloging where possibly-erroring API calls go unhandled. As a stopgap measure, API calls that previously failed and/or used conditions now return Option<T> results to indicate outcome.

Variation: Building The Response With JSON Serialization

Building upon the previous example, a means to easily return structured data to the client would be quite useful. Luckily, Rust ships a generalized serialization scheme in extra::serialize. Additionally, reference implementations for JSON and EBML are provided in extra::json and extra::ebml, respectively.

Instead of reworking the previous example, a get_resp_bytes function is shown, using serialization to build a response.

Rust’s approach to serialization of values is simple and elegant. If your type consists solely of fields that implement Decodable and/or Encodable, you can get serialization support “for free” with a single attribute annotation (#[deriving(Encodable)], above). All of Rust’s primitive values (numbers, strings, etc) implement these traits, as well as fundamental container types like vectors and HashMap<K, V>.

In the above example, an extern mod extra; statement appears at the top of the code (Rust’s convention for linking an external library). Unlike libstd, libextra isn’t linked by default despite being an official Rust library. This allows use‘ing the serialize and json modules. Also, the Decorator trait and MemWriter type are neccesary to get the bytes from serialized Rust values (the inner method is a part of Decorator/MemWriter).

A simple Echo struct is declared for this example. Applying the #[deriving(Encodable)] attribute is our “for free” serialization support. deriving is a shortcut to get common traits for user-defined types.

Within get_resp_bytes, an Echo instance is created and stored in echo. Following that, a MemWriter is created and stored in mem_buf. The MemWriter acts as a std::io::Writer implementation used by the json::Encoder instance used in the example. Depending on how your code was structured, this could be written so that the encoder writes directly into the TcpStream representing the client connection. The rest of the function is concerned with building a byte vector consisting of some canned HTTP response headers, plus the serialized JSON. In this case, since the size of the byte array to-be-returned isn’t known, a growable vector (~[]) is used. Currently, this is the only way to safely build and return vectors values of unknown size from functions.

The encoder instance is kept in a nested scope because, for the duration of its existence, it borrows the mem_buf instance, preventing us from getting at the contents of the MemWriter while the encoder exists. This is a common pattern in Rust programs, where grasping value-lifetime and ownership semantics are crucial to writing programs the compiler accepts.

Learning More About I/O And The Rust Runtime

All of the fundamental traits used in this post (Reader, Writer, Decorator, etc) are a part of the top-level API specified in std::io. The official documentation has a number of examples to get a curious developer started on the path to productivity.

Furthermore, this post in no way endorses crafting your own HTTP servers in rust, as the community already has a superb solution in rust-http.

The official Rust documentation hosts a guide to Runtime, meant to inform developers on the benefits and trade offs of the provided M:N and 1:1 implementations, in addition to what drove the design and how to opt-out.

For more details about general programming languages, see the Safari Books Online resources referenced below.

Not a subscriber? Sign up for a free trial.

Safari Books Online has the content you need

Practical Foundations for Programming Languages offers a fresh perspective on the fundamentals of languages through the use of type theory. Whereas most textbooks on the subject emphasize taxonomy, Harper instead emphasizes genetics, examining the building blocks from which all programming languages are constructed. Language features are manifestations of type structure. The syntax of a language is governed by the constructs that define its types, and its semantics is determined by the interactions among those constructs. The soundness of a language design – the absence of ill-defined programs – follows naturally.

Tags: communication, EBML, Errors, I/O, JSON, Rust, Rust Runtime, Serialization, TCP Server,

One Response to “Network Communication and Serialization in Rust”

  1. Ajinkya

    Great post. I am thinking to buy Practical Foundations for Programming Languages, any one has any feedback for that book?