Note: The cover illustration here shows the 2019 edition. ISBN: 978-1-718500440. This review applies equally to both print editions and to the online book.
This is the official book on Rust. It’s available free online ( doc.rust-lang.org/book ) and also as a paperback book. I read the dead-tree version but referred to the online version while playing with code, so this review is based on both. The online version has the advantage of being more up-to-date, but I didn’t find many differences between the two. This was the first book on Rust I’d read, so it was mostly new material, though I had known of Rust’s goal of eliminating certain classes of bugs by catching common errors at compile time.
The book opens with instructions for installation of the compiler and its associated tools, and illustrates the use of cargo – the package-manager-cum-build-system – by taking the reader through the process of writing a simple command-line number-guessing game. Cargo is a powerful part of the Rust environment, so the treatment here is useful. The programming exercise gives a flavour of the Rust language, but in doing so it uses (without fully explaining) some of the key language elements that are not described until later in the book, which I found unhelpful.
There follows some description of Rust’s basic data types and flow control mechanisms before the book moves on to one of the supposedly ‘difficult’ areas of Rust: ownership. The concept of ownership itself is neither new nor particularly difficult, but Rust’s tracking of ownership of memory is a key part of its correctness checking and the language imposes rules preventing, in particular, the sharing of mutable state. The book attempts to explain these rules and the concepts behind them, but to my mind makes fairly heavy weather of it. The reader is led through a series of examples of code that doesn’t compile before the – actually quite simple – rules for taking and for borrowing resources are eventually spelt out. The authors take this opportunity to explain the sorts of error messages that the compiler may generate when the rules are broken, which hardly seems necessary as the Rust compiler gives quite clear, concise, errors; I think it’s something the reader could have been left discover through trial and error.
Moving on through a description of Rust’s approach to struct and tuple datatypes – which could probably, with benefit, have been lumped together with the earlier section on basic data types – we come to a short chapter on Rust’s enum types and an introduction to matching. Rust’s enums are not simple numbers, like those in (say) C, but are sophisticated discriminated union types. Enums are everywhere in Rust and they deserve to be covered earlier and in more detail. Matching, in Rust, is similar to that of a functional language like Haskell. I felt that this, too, deserved a chapter of its own, early on, with some real-world examples rather than the trivial ones presented here; instead there is an introduction to matching here, and a further chapter near the end of the book.
Next we’re led through Rust’s module system and collection types – concentrating here on string types and the issues caused by the fact that Rust’s strings are Unicode stored in UTF-8 – after which comes ‘Error Handling’. Rust is, unsurprisingly, very big on handling of errors, and we’ve had sneak previews of some of the material here in earlier chapters. I would have preferred to have had a fuller description up-front.
Next comes coverage of Rust’s type traits, generic types, and lifetimes – all in the same chapter. Lifetimes are another of the notoriously ‘difficult’ areas of the language: the idea is simple in concept – the compiler needs to know the lifetimes of references in order to enforce its checking for dangling pointers, and often it needs some help from the programmer. This means that the language includes a special syntax for describing lifetimes and the programmer has to understand it, and when to use it. This is pretty important stuff in Rust and, to my mind, deserves more prominent treatment, earlier in the book. Traits and generics are important too, of course, but they’re not related to lifetimes and not fundamental to Rust’s primary goal of catching errors at compile time, and could have been treated later.
After a section on Rust’s automated testing facilities the reader is led through an exercise in writing a grep-like command-line tool before being introduced to closures and iterators. This is followed by additional coverage of the cargo tool and Rust’s online package repository crates.io before the reader is treated to Rust’s take on smart pointers. The discussion of smart pointers and how they fit into Rust’s compile-time correctness checking scheme is important and deserves to have been covered earlier in the book (but after enums and lifetimes, upon which it depends).
There then follows a chapter on ‘Fearless Concurrency’, which explains why Rust’s refusal to allow shared access to writable state leads naturally to safe threading, and one on Rust’s Object-Oriented features (traits again) and the fuller treatment of patterns and matching that we’ve been looking forward to for the last 12 chapters.
Finally there is a section on advanced features, which includes a discussion of ‘unsafe’ Rust code, which is not guaranteed by the Rust compiler to be correct (this includes code written in C or another alien language), followed by one last worked example in which a multi-threaded web server is coded in Rust. The book ends with some appendices, the last of which contains a brief description of Rust’s macro language.
I didn’t hate this book – it was an interesting read and I learned a lot about Rust – but you’ve probably gathered that I didn’t entirely like it, either. I found the order in which language features were presented to be illogical – the reader is forever being asked to take a feature from a future chapter on trust while working through an example – and I felt that there was a lack of meaningful examples. Although the introduction states that the reader is expected to know another language it goes on to explain a number of quite basic programming concepts while glossing over several things that are peculiar to Rust and should have been covered in more depth. There are diagrams to show the layout in memory of Rust’s strings, for example, even though they’re pretty unsurprising, but no diagrams showing the layout of the smart pointer types or enums with data fields about which I had wondered while reading their descriptions. At times the book left me with more questions than it answered. The index, by the way, is terrible. I referred to it several times, and never found an entry for the thing I was trying to look up.
I felt, at the end, that I could read Rust code, but I wasn’t confident that I knew how to write it. I had no feeling for the idioms of the language – perhaps in part because of the book’s habit of introducing each topic by showing code that doesn’t work and then fixing it.
That said: the free online version of the book is certainly accessible and economical, and although it wasn’t my cup of tea, but it may be yours.