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.
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.
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.
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.
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.
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.
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.
The Rust Design Patterns book is a great read if you’re looking for quick tips on idiomatic Rust-isms.
#[cfg]
now implies #[doc(cfg)]
,
though note that #[doc]
is still unstable.
Asynchronous streams in the standard library will likely be called
AsyncIterator
.
cargo-msrv
is a super handy tool for finding the minimum supported
Rust version (MSRV) for a crate.
insta
is a really neat library for doing snapshot testing.
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).
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>
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 ofx
in a statement such aslet x = &some_variable
.
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.
The page starts out saying
a
Turtle
is more “useful” than some unspecifiedAnimal
in reference to Turtle
being a subtype of Animal
. It is more accurate to say
a
Turtle
is at least as “useful” as some unspecifiedAnimal
as it may be of no more use than Animal
.
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.”
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.
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 […]” .
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)
}
}
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.
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.
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.
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, aCow
uses theToOwned
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.
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.
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.
The last sentence of the second paragraph says
Similarly,
if cfg!(feature = "some-feature")
is equivalent toif true
only if thederive
feature is enabled
but it should say
Similarly,
if cfg!(feature = "some-feature")
is equivalent toif true
only if thesome-feature
feature is enabled
The second sentence of the second paragraph says:
We could fix this by making the
buckets
field visibilitypub(crate)
Which is accurate, but hard to follow. It should instead say:
We could fix this by setting the visibility of the
buckets
field topub(crate)
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),*) => {
// ...
}
}
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
}
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).
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(());
}
}
}
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.
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;
}
}
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);
}
Listing 9-6 says
self.set_len(start + n);
but should say
self.set_len(start + fill);
The statement starting let init
in Listing 9-5 should be indented one
more level than it is.
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;
Listing 9-11 says
unsafe impl<#[may_dangle] T> for Box<T> { /* ... */ }
but should say
unsafe impl<#[may_dangle] T> Drop for Box<T> { /* ... */ }
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.”
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.
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 […]”.
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.
The sentence
Since
Foo
andBar
are zero-sized types, they can be used in place of()
in theextern
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
andBar
are identical toc_void
in size and alignment, they can be used in place of the latter in theextern
method signatures.
Second sentence of last paragraph, change “does no” to “does not”.
“… something like thumbv7m-none-eabi
does not, and doesn’t…”
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
.
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);
}
}