Rust Progress, if let and simple-ssg-rs

I am slowly but consistently learning Rust, reserving approximately an hour each day since my last post almost six weeks ago, and I think I am progressing OK. What follows is a recap of my learning path which might help, especially if you are experienced in C++ and contemplating learning Rust. I will also briefly talk about if let and introduce simple-ssg-rs, a static site generator (like Jekyll, Hugo or Zola) but much simpler and which I created for the sole purpose of exercising Rust.

In general, I think the documentation for learning Rust is quite good. With a good C++ background, however, I can recommend the Rust for C++ programmers tutorial by Nick Cameron as a starting point. After mostly just reading through it, doing occasional exercises on the command line to check my understanding, I felt it is time to get my hands dirty instead of reading more and proceeded with Rustlings. Currently, Rustlings provides 94 small exercises in 23 chapters, each chapter with pointers to recommended reading material in case you get stuck. You have to fix errors or complete code, and provided tests check whether you passed the exercise. Once you do pass, you can view the provided solution for the respective exercise to compare with your solution and I found doing this really helpful. Solving most exercises did not take a lot of time for me, but seeing the provided solution was often quite educating in how to write idiomatic Rust. When you use Rust’s characteristics effectively, code often becomes much more concise but it will take some more time until constructs like if let for pattern-matching-based control flow feel natural to me.

if let

Let’s say you have a call like buf_reader.lines().next(), where buf_reader is a BufReader which performs read operations on an input stream (for example incoming HTTP requests over TCP). lines() returns an iterator over the lines read by the buf_reader, and next() returns the next line of this iterator. The type of a line returned by next() in this case is Option<Result<String, Error>>.

So let’s unpack this a bit: String will be the actual data, a string without newlines. It is wrapped into a Result, because the read operation can fail due to an IO error or when reading something that is not part of the UTF-8 character set. Result is similar to C++23’s std::expected, and can either contain the expected string x as Ok(x) or an error y as Err(y).

Result<String, Error> is further wrapped into an Option, because the iterator might not have another line that next() could return. In this case, next() would return None. If there is another line, it returns Some(z) instead, where z is the Result<String, Error> we just talked about. Option is similar to C++17’s std::optional but neither std::expected nor std::optional are as central or tightly integrated in C++ as Result and Option are in Rust.

Getting back to if let, you can write the following in Rust:

if let Some(Ok(http_request_line)) = buf_reader.lines().next() {
    // Work on the string (http_request_line) ...
} else {
    Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "Got empty request"))
}

This handles all the four cases of Ok / Err, Some / None possible by Option<Result<String, Error>> as returned by buf_reader.lines().next() in a very concise way. The if branch will only be executed when we actually obtained the expected string and conveniently unwraps Some(Ok(..)) so we can directly work with the variable http_request_line. In all other cases, an error will be returned.

C++17 has “if statements with initializer” which somewhat goes into the direction of if let, but it will probably be a while until you can expect the standard library to consistently use std::expected or std::optional. We did not even talk about let-else, while let or the fact that all of these are short forms for the powerful match control flow construct. I have to say I am sold on the mix of conciseness and explicit handling of values it brings.

In any case, if let and match are some of the things you can practice using Rustlings and I can really recommend Rustlings to get started with Rust.

simple-ssg-rs

I finished Rustlings little more than a month ago. Since then, I have been working on a simple static site generator simple-ssg-rs to practice more Rust. It is now able to take a folder full of markdown files including front matter in Yaml to generate HTML pages and an index page. The pages are dumped as HTML files into another folder, and can be served via a little HTTP server. For the HTTP server, I took the “Final Project: Building a Multithreaded Web Server” as a starting point but kept it single-threaded for simplicity for now (there is only one expected user). I currently use the clap, pulldown-cmark, yaml-rust2 and chrono crates for command-line arguments, Markdown, Yaml and time/date handling, respectively. To be honest it was not always easy to decide which dependency to go for, if any.

All in all, simple-ssg-rs is pretty basic but allowed me to have a deeper look of certain aspects of Rust. For example, how dependencies are managed, I/O, error handling, using clippy (the Rust project’s own linter / static code analyzer), and modularizing as well as testing my code. Enough topics to dive deeper into in following blog posts. 😊

I might write a bit more about simple-ssg-rs in another post, but generally having a little toy project and looking up how to use the language in the Rust Book along the way worked pretty well. I am a bit skeptical of using LLMs for coding but occasionally I asked one to suggest how to improve my code, especially, on how to make it more idiomatic Rust. This was helpful in my learning experience, but I would say it is important to struggle yourself first without this kind of help.

In general, I tried to follow best practices to get a better understanding of Rust and you can follow my steps in the git repository on Codeberg. simple-ssg-rs will probably not become a serious project, but mainly stay a playground for exploring Rust. One feature I will definitely add is live reload, I want to regenerate the HTML page when a markdown file changes and trigger a reload in the browser connected to the HTTP server. Let’s see where it goes from there, there is still plenty to learn about Rust. 🙂

In the next posts I will dive a bit deeper in some aspects of the language. Up next is one of the things I have struggled with while writing simple-ssg-rs: the choice and handling of dependencies in the form of crates. So let’s see whether I can get my thoughts in order for the next post!

Posts in this series