Rust 1.0 Alpha just released, and I think it’s about time to dig into it. I have an idea for a very personal side project (just a handy tool for me and my wife to use), and a super simple HTTP (read: basically useless) server fits the bill for one of the requirements.
Let’s be clear: by ‘super simple’ I really mean a server that understands the absolute bare minimum of HTTP to serve a response in the most stupid/simple of ways.
Turns out, if you ignore almost all HTTP features, it’s really quite simple (I think… I’ll probably eat my words later ;)! In the rest of this post I’ll throw some Rust code at you, basically ripped out of the example in the std::io documentation, then walk through my process in learning exactly what the heck is going on.
The Code:
use std::io::{TcpListener, TcpStream};
use std::io::{Acceptor, Listener};
use std::thread::Thread;
fn handle_client(mut stream: TcpStream) {
stream.write_str("HTTP/1.1 200 OK\nContent-Type: text/plain\n\nahoy!");
}
fn main() {
let listener = TcpListener::bind("127.0.0.1:8080");
let mut acceptor = listener.listen();
for stream in acceptor.incoming() {
match stream {
Err(e) => {
println!("connection failed");
},
Ok(stream) => {
Thread::spawn(move|| {
handle_client(stream)
});
},
}
}
drop(acceptor);
}
Perusing over this code, my eye catches on the match
statement first. Boiled down, it looks like this:
My first thought is that this is just a different kind of select
or case
statement – but it turns out that it is so much more.
It’s actually a pattern matching statement that can be used as an expression.
Let’s break that down some:
First, “can be used as an expression” – a match
can actually return a value! This is perfectly legal:
And this actually goes deeper than just the match
statement – almost everything in rust is an expression. If an expression is followed by a semicolon, then the value of the expression is suppressed and it’s treated like a nil
value.
An implication of this is that if the last line of a block is not terminated with a semicolon, it becomes the blocks value. IE:
Here’s some more interesting stuff! The .to_string()
is necessary in this example because "test"
by itself is of type &str
(a reference to a statically allocated string), which is not the type that the println!
macro expects (macro’s are indicated by the exclamation). The .to_string()
method actually converts the &str
value into a String
(dynamically allocated mutable utf-8 string value).
Nifty!
Back to the match
statement – moving past expressions and statements and back to “pattern matching”. The Err(e)
and Ok
are actually enum
values ultimately of the type std::result::Result
.
Enums are powerful in Rust in that they let you structure data, then destructure that data in expressions like match
. So, a method can return a value of an enum type Result
and know exhaustively the possible enum values are Ok
or Err
. That by itself would be good-to-know information, but practically useless in many situations – there’s no other information like an error message or success code, etc. These enums actually allow for sort of ‘embedding’ values into them, so the value or values of an enum can be carried with the enum value.
In the match
statement the value held by the Err
enum value can be pulled out (by destructuring the enum values), like so:
match resultvalue {
Ok(resultvalue) => { /* do something with the resultvalue */ }
Err(errmsg) => { println!("error detected: {}", errmsg); }
}
Conclusion
That’s enough for this time around. Next time will be more learning Rust, as well as getting into the nitty-gritty of what is actually needed to make a minimally useful HTTP server!