Blogs

Book Review: Rust Atomics and Locks

Today’s book review is for Rust Atomics and Locks by Mara Bos. To be honest, I resisted buying this one for a while because it seemed like such a niche topic and one I didn’t expect to use much in my daily programming. However, after reading it, I’m glad I did and would recommend it to anyone who enjoys reading technical books. Some of the reviews even suggest it’s not that Rust-specific, which is true to an extent, but I would personally only recommend it to people with at least some interest in Rust. Some of the chapters are almost1 entirely independent of Rust, such as the discussions of memory ordering and assembly instructions corresponding to atomic operations, but I’m guessing hardcore Rust-haters would still not enjoy it. The chapters implementing concurrency primitives like mutexes2, channels, and condition variables obviously entail much more Rust code, but the code is still pretty short and straightforward, which should make it easy enough to port to other languages.

Anyway, assuming you don’t mind finding a little Rust in a book called Rust Atomics and Locks, the book is pretty good3. I’m not sure if it’s just the dryness of the topic or a shortcoming in the writing, but I did find it to be a bit of a slog in parts. I thoroughly enjoyed the first half of the book or so with the chapters “Basics of Rust Concurrency,” “Atomics,” “Memory Ordering,” and then several practical chapters on building spin locks, channels, and an Arc (atomically reference counted smart pointer). To me, these early conceptual chapters and a few examples are the core of the book’s value proposition. I thought I had a decent handle on Rust’s concurrency basics, having used things like channels and rayon and even an AtomicUsize to share a counter between rayon threads, but the refresher in the first chapter was very useful. In fact, it helped me to solve a silly issue I had run into with my window manager, where I couldn’t store one of my structs in a static variable because it held a raw pointer, which isn’t Sync. I just needed to write unsafe impl Sync for MyStruct {}, which I had seen before but had forgotten about.

I would also include chapter 7 in the valuable portion of the book, but this is also where things started to get a little slow. This chapter is titled “Understanding the Processor” and covers how the Rust atomic code written in the earlier chapters gets translated into assembly instructions for x86 and ARM processors. On its surface this is pretty cool, but as it turns out, almost all of the complicated memory ordering decisions from the earlier chapters compile down to the same instructions, at least on x86, which is already a “strongly-ordered” architecture. Probably the coolest part of this chapter was a demonstration of this difference from the weakly-ordered ARM processor, where code written with the wrong memory orderings worked as desired on x86 but failed on ARM. Otherwise, the chapter is a fairly repetitive walk through all of the atomic operations and their corresponding assembly instructions, summarized at the end in a big table.

Chapter 8, “Operating System Primitives,” also has its highlights. For me, the discussion of the Linux futex was probably the most valuable, combined with the realization that none of the code written in earlier chapters had really handled blocking threads that were waiting (we had mostly used spinning up to that point). There’s some useful discussion about the optimization of avoiding syscalls (like the futex operations) for fast code here, but also a description of the futex-like calls on other operating systems like macOS and Windows. Obviously this is important for library authors, but as a Linux user and unlikely cross-platform library author, I didn’t really care too much about these sections.

Finally, chapter 9 includes several variations on the mutex theme. At face value, this chapter should have been the culmination of the entire book, but most of the excitement, for me at least, wore off in the earlier practical chapters, and throwing in a few futex-like wait and wake operations didn’t feel too earth-shattering. Chapter 10 just describes a bunch of possible future projects to play with atomics and locks on your own.

As usual, I feel like I’ve written a more negative review than I intended, but at the same time, I really did feel pretty bored by the end of the book. My overall feeling toward the book is basically what I feel when I’m reading good crate documentation. It’s thorough and well-written enough, and I definitely want to have it available as a reference when writing this kind of code, but it wasn’t the most enjoyable technical book I’ve read, especially recently4. As one double-edged compliment, part of what put me off buying this originally was its relatively short length of only 220 pages. After reading it, I’m much happier with this length. While I’m here, I’ll pile on one more complaint that the typesetting didn’t seem quite up to O’Reilly’s usually standards, but this could just be my imagination.

I’ll close the review emphasizing that I am glad I bought and read this book, and I will certainly keep it nearby if I ever try writing low-level concurrency primitives in the future. If that’s part of your job or hobby project, this book might be an absolute essential. Otherwise, I think it’s certainly still worth your time and money (especially if you get it on a ~50% sale like I did) to improve your understanding of the existing concurrency primitives in the Rust standard library, their underlying system calls, and even their corresponding assembly/machine instructions. I was going to give the book a 6/10, but, again, thinking about this in terms of letter grades, it’s probably more of a B or even B+ book. The information seems very valuable, but the presentation and writing quality is lacking in some important ways that definitely keep it from making an A. I guess I’ll give it an 8/10.