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 pretty much hate every piece of content i read about rust. its either extremely muddle down with beginner topics mixed in or is just so pretentious it hurts

Rust for Rustaceans is not the case. Its a great book. HIGHLY recommend.

It’s a very dense book. It’s light on code, no pictures. Top to bottom of just text. It doesn’t have projects to follow along with, you don’t build anything, you don’t have practice exercises. I think it’s important to note that before anyone get this book. […]

All said, it’s a great book and one of the very few programming-related book I’ve ever completely read from front to back.

Amazing resource to dive into the nitty-gritty details of how things work under the hood. The author has a great style of explaining these details without unnecessarily overcomplicating matters. I can see this becoming a classic for the ages.

Many of the more confusing language features are covered in-depth in a way that’s unlikely to go out of date, and a lot of what you’ll learn will help you in other programming languages as well. This book is fairly dense, but by the end you should be ready to tackle real-world projects in a way that might feel intimidating right now. Overall, it’s a must-buy for anyone who wants to take their rust knowledge from dabbling to professional.

This is a great book, advancing your knowledge beyond all of the beginner books. […] “Rust for Rustaceans” is just what we need next, a book that covers numerous intermediate topics in a coherent and instructive way. It shows not the mechanics of the language as beginner books do, but the hows and whys of how to actually use Rust, how to think about writing code in Rust, and how to understand how to work with Rust.

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

The book doesn’t really talk about unwrap and expect, and doesn’t give guidance on when they are and aren’t appropriate. One day it probably will, and it likely look a lot like Andrew Gallant’s recommendations in his excellent “Using unwrap() in Rust is Okay” blog post.

» See also page 64 (chapter 4), "Propagating Errors".

matklad’s blog is an excellent resource for great and concise Rust advice and tricks. See, in particular, the One Hundred Lines of Rust series.

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

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).

p15 (ch. 1)

Listing 1-10 uses the signature

fn next(&self) -> Option<Self::Item>

for Iterator::next, but it should be

fn next(&mut self) -> Option<Self::Item>

Reported by Ihor Taranenko on Aug 7, 2022.

p16 (ch. 1)

This page should have a note between the second and third paragraph that reads

The 'a in the example types listed here do not refer to a generic lifetime gathered from <'a>. Instead, it refers to some concrete lifetime that is not 'static, such as that assigned to the type of x in a statement such as let x = &some_variable.

Reported by Chris00 on Aug 13, 2022.

p16 (ch. 1)

The sentence that starts “All types have a variance” should instead read:

All types with generic parameters (be they lifetime or type parameters) have a variance with respect to those parameters. That variance defines what …

Types with no generic parameters do not “have a variance”. Variance is always defined in relation to a parameter.

Reported by Chris00 on Aug 13, 2022.

p16 (ch. 1)

The page starts out saying

a Turtle is more “useful” than some unspecified Animal

in reference to Turtle being a subtype of Animal. It is more accurate to say

a Turtle is at least as “useful” as some unspecified Animal

as it may be of no more use than Animal.

Reported by korkje on Nov 9, 2022.

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.

p27 (ch. 1)

Listing 2-3 reads:

impl String {
    pub fn contains(&self, p: &dyn Pattern) -> bool {
        p.is_contained_in(&*self)
    }
}

The &* before self has no effect and should be removed. The listing would then read:

impl String {
    pub fn contains(&self, p: &dyn Pattern) -> bool {
        p.is_contained_in(self)
    }
}

Reported by Kyle Strand on Aug 27, 2023.

p30 (ch. 2)

In impl<P1..=Pn> ForeignTrait<T1..=Tn> for T0, Tn should be Tm since there isn’t a requirement that the number of generic type parameters matches the number of generic parameters of ForeignTrait.

Interestingly enough, the RFC uses Tn, though I couldn’t find any rationale for why, and it is almost certainly just a typo. See also this discussion.

Reported by Chris00 on Aug 13, 2022.

p40 (ch. 3)

The “Wrapper Types” section suggests that Deref functions a little like inheritance, which may mislead readers to using it to emulate OO in Rust, which is a bad idea. At the time of writing, the API guidelines recommend only using Deref for smart pointer types (see also this SO answer), though this may change to apply more broadly to is-a newtypes, which is what the book should also recommend.

Reported by alex_xiong_ on Dec 4, 2022.

p41 (ch. 3)

The box “Deref and Inherent Methods” should say

methods on T that take &self

That is, it should say &self rather than self to match the actual definition of the Deref trait.

The box should also mention DerefMut and AsRef, both of which have the same auto-dereference behavior.

Reported by Chris00 on Aug 13, 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.

p49 (ch. 3)

The contents of the impl Default block at (3), as well as the body of the various methods in the following impl blocks, have all been elided. This was done for clarity, but the elided items should be indicated with … within each of the currently-empty {} to indicate that this is the case.

Reported by Tom on Mar 4, 2023.

p58 (ch. 4)

The second to last paragraph is missing the word “what”. It should read:

… to provide the caller with as much information about what went wrong as possible.

Reported by styvane on Jul 17, 2022.

p70 (ch. 5)

The last sentence of the second paragraph says

Similarly, if cfg!(feature = "some-feature") is equivalent to if true only if the derive feature is enabled

but it should say

Similarly, if cfg!(feature = "some-feature") is equivalent to if true only if the some-feature feature is enabled

Reported by Joel Posti on Aug 14, 2023.

p89 (ch. 6)

The second sentence of the second paragraph says:

We could fix this by making the buckets field visibility pub(crate)

Which is accurate, but hard to follow. It should instead say:

We could fix this by setting the visibility of the buckets field to pub(crate)

Reported by Joel Posti on Sep 13, 2023.

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.

p103 (ch. 7)

Listing 7-2 closes the test_battery macro invocation with a closing paren and semicolon );:

...
test_battery! {
  u8 as u8_tests,
  // ...
  i128 as i128_tests
);

… instead of a closing brace }:

...
test_battery! {
  u8 as u8_tests,
  // ...
  i128 as i128_tests
}

Reported by nmarley on Jan 5, 2023.

p121 (ch. 8)

The various listings and text in the sub-section “Ergonomic Futures” in chapter 8 uses Receiver::next and Receiver::receive interchangeably. This is confusing, since it means the text occasionally does not agree with the listed code (e.g., the last paragraph on page 125 refers to Receiver::next in Listing 8-4, which has no such call).

There is no good reason for some of the listings to use one and others to use the other.

The text and code should be updated such that every call to .receive and mention of Receiver::receive should be changed to .next and Receiver::next respectively. It should use next consistently as that is the generally agreed-upon convention for getting the next value from a stream (which Receiver represents).

Reported by jonhoo on Dec 17, 2023.

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 is missing a loop around the call to poll. It 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.

p126 (ch. 8)

The fourth sentence of the “Pin and Unpin” section says “In the code from Listing 8-5, the future that rx.next() returns […]”. While technically correct, it should instead refer to Listing 8-6, since the rest of the paragraph is alluding to the desugared code of the generator.

Reported by david-perez on Aug 12, 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.

p152 (ch. 9)

Listing 9-4 declares pub trait methods as public

pub unsafe trait GlobalAlloc {
    pub unsafe fn alloc(&self, layout: Layout) -> *mut u8;
    pub unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout);
}

but pub is not permitted on pub trait methods because it’s implied

pub unsafe trait GlobalAlloc {
    unsafe fn alloc(&self, layout: Layout) -> *mut u8;
    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout);
}

Reported by necabo on Feb 20, 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.

p187 (ch. 10)

In paragraph 2 of the COMPARE_EXCHANGE_WEAK box, the text reads:

For example, ARM processors instead have locked load and conditional store operations, where a conditional store will fail if the value read by an associated locked load has not been written to since the load.

The “not” there is erroneous, and the sentence is unclear. The sentence should read:

For example, ARM processors instead have locked load and conditional store operations, where a conditional store will fail if the value read by an associated locked load has been written to at all, even with the same value, since the load.

Reported by nicarran on Jan 31, 2024.

p197 (ch. 11)

The fourth sentence of the “Link Kinds” section has an extra space before a comma. It says “[…] the binary produced by the compiler , and thus […]”, but should instead read “[…] the binary produced by the compiler, and thus […]”.

Reported by david-perez on Aug 29, 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.

p207 (ch. 11)

The sentence

Since Foo and Bar are zero-sized types, they can be used in place of () in the extern method signatures.

in the final paragraph of the “Pointer Confusion” section is inaccurate. Both Foo and Bar are #[repr(transparent)] wrappers over a c_void, but c_void is not equivalent to (), and its size is in fact 1 rather than 0.

For a deeper explanation of why c_void must not be zero-sized, see https://github.com/rust-lang/rust/blob/1be5c8f90912c446ecbdc405cbc4a89f9acd20fd/library/core/src/ffi.rs#L26-L33

The sentence in question should be replaced with:

Since Foo and Bar are identical to c_void in size and alignment, they can be used in place of the latter in the extern method signatures.

Reported by on Feb 1, 2022.

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.

p229 (ch. 13)

The paragraph on Cargo disk usage suggests setting [build] target in your ~/.cargo/config.toml to use a shared artefacts directory but it should be [build] target-dir.

Reported by wezm on Feb 5, 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(false, Ordering::Release);
    }
}