Asynchronous Rust 2
This is a follow-up to my earlier complaint about asynchronous IO and event loops in Rust.
Since that time, the Rust crates Tokio and Futures have had stable releases, and I’ve been playing with them a bit.
Anyway, reproducing from the Tokio docs, this is an asynchronous echo server:
extern crate futures;
extern crate tokio_core;
use futures::{Future, Stream};
use tokio_core::io::{copy, Io};
use tokio_core::net::TcpListener;
use tokio_core::reactor::Core;
fn main() {
let mut core = Core::new().unwrap();
let handle = core.handle();
let addr = "127.0.0.1:12345".parse().unwrap();
let sock = TcpListener::bind(&addr, &handle).unwrap();
let server = sock.incoming().for_each(|(sock, _)| {
let (reader, writer) = sock.split();
let bytes_copied = copy(reader, writer);
let handle_conn = bytes_copied.map(|amt| {
println!("wrote {} bytes", amt)
}).map_err(|err| {
println!("IO error {:?}", err)
});
handle.spawn(handle_conn);
Ok(())
});
core.run(server).unwrap();
}
For comparison, here is a strictly less functional version in C:
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#define BUFLEN 2048
int main() {
int ret;
int fd = socket(PF_INET, SOCK_STREAM, 0);
if (fd == -1) {
exit(1);
}
struct sockaddr_in myaddr;
myaddr.sin_family = AF_INET;
myaddr.sin_port = htons(12345);
myaddr.sin_addr.s_addr = INADDR_ANY;
ret = bind(fd, (struct sockaddr *) &myaddr, sizeof(myaddr));
if (ret != 0) {
exit(1);
}
ret = listen(fd, 10);
if (ret != 0) {
exit(1);
}
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(fd, &readfds);
int nfds = fd + 1;
while (1) {
ret = select(nfds, &readfds, NULL, NULL, NULL);
if (ret < 0) {
exit(1);
}
if (FD_ISSET(fd, &readfds)) {
int client = accept(fd, NULL, NULL);
if (client == -1) {
exit(1);
}
FD_SET(client, &readfds);
nfds += 1;
continue;
}
for (int i = fd + 1; i < nfds; i++) {
char buf[BUFLEN];
ssize_t len = recv(i, &buf, BUFLEN - 1, MSG_DONTWAIT);
if (len <= 0) {
close(i);
}
buf[len] = '\0';
ret = send(i, &buf, len, MSG_DONTWAIT);
if (ret != len) {
close(i);
}
}
}
}
The C version is longer, more involved, less understandable to people not intimately familiar with network code, and let me emphasize: less usable. It also includes a cast, right there in the code.
Tokio uses mio, which I criticized in my previous post for being impossible to use by mortals (i.e., those picking it up for the first time are presented an impossible learning curve). As far as I can tell, my complaints have been addressed, and I am happy. I wish we had had this support sooner.