Pretty much the only way I get things to stay in my head is to do them, and then document what I did.
Task one: Doing the Multithreaded Server Exercise from the book.
Yesterday, I ripped through the Rust "Writing a multithreaded web server" exercise, as I don't have much experience with writing network code with Rust. This is the last exercise in The Rust Book (the one from O'Reilly, and also the one that ships free with the language as a collecition of HTML pages), and it's a pretty good exercise, but as I was working on it I discovered there are two bugs with the documentation.
The objective of the exercise is to creata simple session handler named handle_connection()
, that takes a request and sends a response. The engine is so primitive it can only serve three pages: a hello world page, a hello world page with an active delay (to simulate slow networking), and a 404 page.
Bug 1: Streams must be shut down
The last line of handle_connection
flushes the socket and then the connection ends. Theoretically, this means that the socket should shutdown, but it doesn't seem to work that way. There were two ways of fixing it.
The first was to call shutdown manually:
<code class="sourceCode rust"><span class="kw">use</span> std::net::Shutdown;
<span class="kw">fn</span> handle_connection(<span class="kw">mut</span> stream: TcpStream) <span class="op">{</span>
...
stream
.shutdown(Shutdown::Both)
.expect(<span class="st">"shutdown call failed"</span>);</code>
Doing this worked best, as clients with "keepalive" active got the right message and shut the connection gracefully. Without this instruction, the socket would stay open and the server would wait for a timeout.
The second was to move the stream. In main, change the execute line to:
<code class="sourceCode rust"> pool.execute(<span class="kw">move</span> || handle_connection(stream));</code>
This would force the stream to die when the handle function terminated, but it would result in a "Error: Connection reset by peer" message from older W3C HTTP libraries (like thosed use in the console applications links). Only by closing the socket gracefully could both problems be eliminated.
Bug 2: Ergonomics marches on!
This is less of a bug than it is an aesthetic point. I'm currently running the latest and greatest of Rust stable: 1.37. The book describes a trait called FnBox
that works around an ownership issue:
<code class="sourceCode rust"><span class="kw">trait</span> FnBox <span class="op">{</span>
<span class="kw">fn</span> call_box(<span class="kw">self</span>: <span class="dt">Box</span><<span class="kw">Self</span>>);
<span class="op">}</span>
<span class="kw">impl</span><F: <span class="bu">FnOnce</span>()> FnBox <span class="kw">for</span> F <span class="op">{</span>
<span class="kw">fn</span> call_box(<span class="kw">self</span>: <span class="dt">Box</span><F>) <span class="op">{</span>
(*<span class="kw">self</span>)()
<span class="op">}</span>
<span class="op">}</span>
...
job.call_box();</code>
This method is intended to replace this syntax, which prior to Rust 1.37 threw a compile-time error: (*job)()
. Here, job
is our closure produced by the pool.execute
function above. The problem is that Rust doesn't know how big job
will be and so can't allocate stack space for it. By using this trick, we tell Rust that we're explicitly and finally taking ownership of the FnOnce()
object in order to run it once.
This is no longer necessary. As of Rust 1.37, you can throw away the entire FnBox
shenanigans and replace it with:
<code class="sourceCode rust"> job();</code>
Task two: Draw the rest of the m11g* owl.
PingCAP has a wonderful tutorial designed to teach new programmers how to program in Rust. Starting from cargo new
, you end with an implementation of a high-performance, caching key-value storage server and a client to go with it.
Their tutorial is very sparse. You go from having almost nothing to having a working k/v store very quickly. I implemented the cheapest in-memory one-shot variant I could to get through the first lesson and learned a ton of things about the command line management library, clap
], how to use the #![deny(missing_docs)]
syntax to guide my documentation efforts, and how to use the failure
crate.
I'm afraid, however, that it's a bit more than I can chew, and I may be putting the other lessons aside, in order to start wrapping up some other projects that I'd like to concentrate on.
Task three: The Internet is for Porn
I make no secret of the fact that I'm as fond of the smut as any other human being. I joke that "porn, video games, and anime" are the foundation of my career. With Tumblr gone, I've been trying to cobble together a couple of other streams to cater to my interests, and I just find it interesting that my toolset is pretty much as diverse as my tastes.
I won't post the results here as they're very idiosyncratic. I wrote my cataloging tool for Pixiv in Perl, the one for Twitter in Hy, the archiving tool for Tumblr in straight Python, and some rather smaller and more obscure ones in Bash. I haven't written Perl in a while, and it was an interesting struggle but not a difficult one. Perl, being the first second language I was paid to work in professionally (truthfully: the first was COBOL w/DB2, but don't tell anyone!) and the language that put food on the family for the first seven years of that professional life, is probably embedded in my DNA like a tick, never to be fully erased.
- The words "internationalization" and "localization," and now "Kubernetes" are all commonly shortened to "i18n", "l10n", and "k8s", where the number in the middle indicates the number of letters elided. You're welcome to figure out what 11 letters go between 'm11g' on your own.