Rust for Rustaceans book cover

Rust for Rustaceans by Jon Gjengset

For developers who’ve mastered the basics, this book is the next step on your way to professional-level programming in Rust. It covers everything you need to build and maintain larger code bases, write powerful and flexible applications and libraries, and confidently expand the scope and complexity

You can buy the book directly from the publisher, which nets you the ebook for free if you get the print book, or you can get it from Amazon or a number of other retailers.

More about the book

You can hear Jon talk about the book on the Are We Podcast Yet podcast as well as on Rustacean Station. No Starch (the publisher) also conducted a longer text-based interview. If you’re after a quick survey of Jon’s writing process, give his Writes With interview a read.

Errata for the book is listed further down on this page.

What readers say

If you’ve read [The Rust Programming Language] and want to know where to go next, I highly recommend picking up Rust for Rustaceans.

I am so glad this book exists.

I am enjoying digging into “Rust for Rustaceans” by @jonhoo. It’s slender and full of insight.

I can’t say that this book blew my mind, but it has a unique position - it targets people who already know rust and want to level up. And it delivers. If you don’t know rust it will be pretty much inaccessible, but if you - it’s a joy to read. […]

I recently bought @jonhoo’s Rust for Rustaceans and wow am I impressed. In the first chapter he managed to teach me something I thought I knew, and better than any other explanations I’ve seen (subtyping and variance). 9.99/10, lost .01 point cause ferris kinda creepy /j

Just read the first chapter of Rust for Rustaceans by @jonhoo.

Even though most of the topics were familiar, I’ve picked up on so many new ways to conceptualize topics such as lifetimes and borrowing.

Can’t wait to study the entire book 🦀.

It’s probably the only book on Rust for non-beginners (which kinda puts some pressure, doesn’t it? ;>). Fortunately - it’s good, even very good. […]

Finished “Rust For Rustaceans” and it’s a remarkable work. Perfect to elevate your education/inspiration after consuming some of the abundant intro material. It’s also so well written and erudite to be rewarding even for the most advanced of 🦀 users. Hats off to @jonhoo!

Reading @jonhoo‘s book Rust for Rustaceans, and barely two chapters in and it’s already one of the best intermediate to advanced Rust learning material I’ve come across.

I was on a long flight during it picked up @jonhoo Rust for rustaceans oh boy what a read that was. I understood lots of concepts related to compiler memory layouts byte alignment etc…A worth read of the book for every programmer.

Updates since the book's release

A while ago, Julio Merino proposed using the builder pattern to define test scenarios, which I think is a great idea. In a future edition of the book I’d probably include a section in the testing chapter on “test patterns”, where that strategy would certainly feature.

» See also page 92 (chapter 6), "Additional Testing Tools".

The Rust Design Patterns book is a great read if you’re looking for quick tips on idiomatic Rust-isms.

» See also page 241 (chapter 13), "Learn by Reading".

MSRV can now be set in Cargo.toml.

» See also page 81 (chapter 5), "Minimum Supported Rust Version".

#[cfg] now implies #[doc(cfg)], though note that #[doc] is still unstable.

» See also page 78 (chapter 5), "Conditional Compilation".

Asynchronous streams in the standard library will likely be called AsyncIterator.

» See also page 124 (chapter 8), "NOTE: You may have noticed …".

cargo-msrv is a super handy tool for finding the minimum supported Rust version (MSRV) for a crate.

» See also page 81 (chapter 5), "Minimum Supported Rust Version".

» See also page 224 (chapter 13), "Tools".

insta is a really neat library for doing snapshot testing.

Errata

If you spot what you believe to be an error in the book, please submit a PR adding it to the _errata folder in the repository for this site (example).

p17 (ch. 1)

The end of the penultimate paragraph reads “[…] and it reports that the list is still mutably borrowed.” There is no list in this example. It should say “[…] and it reports that s is still mutably borrowed.”

Reported by david-perez on Dec 24, 2021.

p26 (ch. 2)

In the “non-generic inner functions” info box in the “traits and trait bounds” section, the phrase “you can instead declare such a helper function outside the method instead” contains the word “instead” twice.

p27 (ch. 2)

In the last paragraph “[…] all the compiler can do for find in Listing 2-2 […]”: Listing 2-2 does not have a find function. It should say “[…] all the compiler can do for contains in Listing 2-2 […]” .

Reported by weigangd on Jan 12, 2022.

p45 (ch. 3)

The sentence “For example, String::from_utf8_lossy needs to take ownership of the byte sequence that is passed to it only if it contains invalid UTF-8 sequences.” in the fourth paragraph of the “Borrowed vs. Owned” section is inaccurate. String::from_utf8_lossy never takes ownership of the input byte sequence. What it does is it leverages Cow<'a, str> to either return the same input bytes reinterpreted as a string slice if they are valid UTF-8, or clone only the valid UTF-8 sequences into a newly-allocated String otherwise.

The paragraph in question should be replaced with:

Sometimes, you don’t know if your code must own data or not, as it is runtime dependent. For this, the Cow type is your friend. It lets you represent data that may be owned by holding either a reference or an owned value. If asked to produce an owned value when it only has a reference, a Cow uses the ToOwned trait to make one behind the scenes, usually by cloning. Cow is typically used in return types to represent functions that sometimes allocate. For example, String::from_utf8_lossy allocates only if the input contains invalid UTF-8. Cow can also be used in arguments for functions that can sometimes make use of owned inputs, but that’s rarer in practice.

Reported by david-perez on Dec 26, 2021.

p103 (ch. 7)

Listing 7-1 has three closing parens

macro_rules! test_battery {
  ($($t:ty as $name:ident),*)) => {
    // ...
  }
}

but should have only two

macro_rules! test_battery {
  ($($t:ty as $name:ident),*) => {
    // ...
  }
}

Reported by AJ ONeal on Jan 9, 2022.

p125 (ch. 8)

Listing 8-6 says

generator fn forward<T>(rx: Receiver<T>, tx: Sender<T>) {
    loop {
        let mut f = rx.next();
        let r = if let Poll::Ready(r) = f.poll() {
            r
        } else {
            yield
        };
        if let Some(t) = r {
            let mut f = tx.send(t);
            let _ = if let Poll::Ready(r) = f.poll() {
                r
            } else {
                yield
            };
        } else {
            break Poll::Ready(());
        }
    }
}

but should say

generator fn forward<T>(rx: Receiver<T>, tx: Sender<T>) {
    loop {
        let mut f = rx.next();
        let r = loop {
            if let Poll::Ready(r) = f.poll() {
                break r
            } else {
                yield
            }
        };
        if let Some(t) = r {
            let mut f = tx.send(t);
            let _ = loop {
                if let Poll::Ready(r) = f.poll() {
                    break r
                } else {
                    yield
                }
            };
        } else {
            break Poll::Ready(());
        }
    }
}

Reported by ilya-epifanov on Jan 9, 2022.

p142 (ch. 9)

Listing 9-1 says

impl<T> SomeType<T> {
    pub unsafe fn decr(&self) {
        self.some_usize -= 1;
    }
}

but should say

impl<T> SomeType<T> {
    pub unsafe fn decr(&mut self) {
        self.some_usize -= 1;
    }
}

Reported by Callum Iddon on Jan 7, 2022.

p158 (ch. 9)

Listing 9-6 says

self.set_len(start + n);

but should say

self.set_len(start + fill);

Reported by monoid on Dec 22, 2021.

p158 (ch. 9)

The statement starting let init in Listing 9-5 should be indented one more level than it is.

Reported by jonhoo on Dec 23, 2021.

p161 (ch. 9)

Listing 9-10 says

fn barify<a>(_: &a mut i32) -> Bar<Foo<a>> { .. }
let mut x = true;
let foo = barify(&mut x);
x = false;

but should say

fn barify<'a>(_: &'a mut bool) -> Bar<Foo<'a>> { .. }
let mut x = true;
let foo = barify(&mut x);
x = false;

Reported by Callum Iddon on Jan 11, 2022.

p162 (ch. 9)

Listing 9-11 says

unsafe impl<#[may_dangle] T> for Box<T> { /* ... */ }

but should say

unsafe impl<#[may_dangle] T> Drop for Box<T> { /* ... */ }

Reported by Nick Massey on Oct 7, 2021.

p177 (ch. 10)

In the “Memory Operations” section, the second sentence is hard to follow. Alter to something like:

“In reality, there’s a lot of machinery between code that uses a variable and the actual CPU instructions that access your memory hardware.”

Reported by David Drysdale on Jan 9, 2022.

p202 (ch. 11)

The sample gives Option<*mut T> as niche optimization used across FFI boundaries. However, the *mut T is nullable, and is not subject to the niche optimization. So, Option<NotNull<T>> should be used as an example.

Reported by monoid on Dec 27, 2021.

p221 (ch. 12)

Second sentence of last paragraph, change “does no” to “does not”.

“… something like thumbv7m-none-eabi does not, and doesn’t…”

Reported by David Drysdale on Jan 10, 2022.

p235 (ch. 13)

Listing 13-3 says

impl Drop for DropGuard<'_> {
    fn drop(&mut self) {
        lock.store(true, Ordering::Release);
    }
}

but should say

impl Drop for DropGuard<'_> {
    fn drop(&mut self) {
        self.0.store(true, Ordering::Release);
    }
}