an exception to the general advice in void wildcard imports).
• The normal visibility rules for modules mean that a unit test has the ability to use
anything from the parent module, whether it is pub or not. This allows for “open-
box” testing of the code, where the unit tests exercise internal features that aren’t
visible to normal users.
• The test code makes use of expect() or unwrap() for its expected results. The
advice in ’t really relevant for test-only code, where panic! is used to
signal a failing test. Similarly, the test code also checks expected results with
assert_eq!, which will panic on failure.
• The code under test includes a function that panics on some kinds of invalid
input; to exercise that, there’s a unit test function that’s marked with the
#[should_panic] attribute. This might be needed when testing an internal func‐
tion that normally expects the rest of the code to respect its invariants and pre‐
conditions, or it might be a public function that has some reason to ignore the
. (Such a function should have a “Panics” section in its doc com‐
ment, as described in
suggests not documenting things that are already expressed by the type system. Similarly, there’s no need to test things that are guaranteed by the type system. If
your enum types start holding values that aren’t in the list of allowed variants, you’ve
got bigger problems than a failing unit test!
However, if your code relies on specific functionality from your dependencies, it can
be helpful to include basic tests of that functionality. The aim here is not to repeat
testing that’s already done by the dependency itself but instead to have an early warn‐
ing system that indicates whether the behavior that you need from the dependency
has changed—separately from whether the public API signature has changed, as indi‐
cated by the semantic version n
228 | Chapter 5: Tooling
Integration Tests
The other common form of test included with a Rust project is integration tests, held
under tests/. Each file in that directory is run as a separate test program that executes
all of the functions marked with #[test].
Integration tests do not have access to crate internals and so act as behavior tests that
can exercise only the public API of the crate.
Doc Tests
described the inclusion of short code samples in documentation comments,
to illustrate the use of a particular public API item. Each such chunk of code is
enclosed in an implicit fn main() { ... } and run as part of cargo test, effectively
making it an additional test case for your code, known as a doc test. Individual tests
can also be executed selectively by running cargo test --doc <item-name>.
Regularly running tests as part of your CI environment your
code samples don’t drift too far from the current reality of your API.
Examples
also described the ability to provide example programs that exercise your
public API. Each Rust file under examples/ (or each subdirectory under examples/
that includes a main.rs) can be run as a standalone binary with cargo run --
example <name> or cargo test --example <name>.
These programs have access to only the public API of your crate and are intended to
illustrate the use of your API as a whole. Examples are not specifically designated as
test code (no #[test], no #[cfg(test)]), and they’re a poor place to put code that
exercises obscure nooks and crannies of your crate—particularly as examples are not
run by cargo test by default.
Nevertheless, it’s a good idea to ensure that your CI system () builds and runs
all the associated examples for a crate (with cargo test --examples), because it can
act as a good early warning system for regressions that are likely to affect lots of users.
As noted, if your examples demonstrate mainline use of your API, then a failure in
the examples implies that something significant is wrong:
• If it’s a genuine bug, then it’s likely to affect lots of users—the very nature of
example code means that users are likely to have copied, pasted, and adapted the
example.
• If it’s an intended change to the API, then the examples need to be updated to
match. A change to the API also implies a backward incompatibility, so if the
Item 30: Write more than unit tests | 229
crate is published, then the semantic version number needs a corresponding
update to indicate this (
The likelihood of users copying and pasting example code means that it should have a
different style than test code. In line with ple for your users by avoiding unwrap() calls for Results. Instead, make each example’s
main() function return something like Result<(), Box<dyn Error>>, and then use
the question mark operator throughout ().
Benchmarks
attempts to persuade you that fully optimizing the performance of your code
isn’t always necessary. Nevertheless, there are definitely times when performance is
critical, and if that’s the case, then it’s a good idea to measure and track that perfor‐
mance. Having benchmarks that are run regularly (e.g., as part of CI;
you to detect when changes to the code or the toolchains adversely affect that perfor‐
mance.
The command runs special test cases that repeatedly perform an operation, and emits average timing information for the operation. At the time of writing,
support for benchmarks is not stable, so the precise command may need to be cargo
+nightly bench. (Rust’s unstable features, including the feature used here, are described in .)
However, there’s a danger that compiler optimizations may give misleading results,
particularly if you restrict the operation that’s being performed to a small subset of
the real code. Consider a simple arithmetic function:
pub fn factorial(n: u128) -> u128 {
match n {
0 => 1,
n => n * factorial(n - 1),
}
}
A naive benchmark for this code:
#![feature(test)]
extern crate test;
#[bench]
fn bench_factorial(b: & mut test::Bencher) {
b.iter(|| {
let result = factorial(15);
assert_eq!(result, 1_307_674_368_000);
});
}
gives incredibly positive results:
230 | Chapter 5: Tooling
test bench_factorial ... bench: 0 ns/iter (+/- 0)
With fixed inputs and a small amount of code under test, the compiler is able to opti‐
mize away the iteration and directly emit the result, leading to an unrealistically opti‐
mistic result.
’s an identity function
(their italics) to pessimize.
Moving the benchmark code to use this hint:
#[bench]
fn bench_factorial(b: & mut test::Bencher) {
b.iter(|| {
let result = factorial(std::hint::black_box(15));
assert_eq!(result, 1_307_674_368_000);
});
}
gives more realistic results:
test blackboxed::bench_factorial ... bench: 16 ns/iter (+/- 3)
The
emitted by the compiler, which may make it obvious when the compiler has per‐
formed optimizations that would be unrealistic for code running a real scenario.
Finally, if you are including benchmarks for your Rust code, the crate may provide an alternat is more convenient (it runs with stable Rust) and more fully featured (it has support for
statistics and graphs).
Fuzz Testing
Fuzz testing is the process of exposing code to randomized inputs in the hope of find‐
ing bugs, particularly crashes that result from those inputs. Although this can be a
useful technique in general, it becomes much more important when your code is
exposed to inputs that may be controlled by someone who is deliberately trying to
attack the code—so you should run fuzz tests if your code is exposed to potential
attackers.
Historically, the majority of defects in C/C++ code that have been exposed by fuzzers
have been memory safety problems, typically found by combining fuzz testing with
runtime instrumentation (e.g., or ) of memory access patterns.
Rust is immune to some (but not all) of these memory safety problems, particularly
when there is no unsafe code involved (owever, Rust does not prevent
Item 30: Write more than unit tests | 231
bugs in general, and a code path that triggers a panic! (see ) can still result in a denial-of-service (DoS) attack on the codebase as a whole.
The most effective forms of fuzz testing are coverage-guided: the test infrastructure
monitors which parts of the code are executed and favors random mutations of the
inputs that explore new code paths.
heavyweight champion of this technique, but in more recent years equivalent func‐
tionality has been included in the LL
The Rust compiler is built on LLVM, and so the subcommand exposes libFuzzer functionality for Rust (albeit for only a limited number of platforms).
The primary requirement for a fuzz test is to identify an entrypoint of your code that
takes (or can be adapted to take) arbitrary bytes of data as input:
U N D E S I R E D B E H A V I O R
/// Determine if the input starts with "FUZZ".
pub fn is_fuzz(data: &[u8]) -> bool {
if data.len() >= 3 /* oops */
&& data[0] == b'F'
&& data[1] == b'U'
&& data[2] == b'Z'
&& data[3] == b'Z'
{
true
} else {
false
}
}
With a target entrypoint identified, the
arrange the fuzzing subproject. At its core is a small driver that connects the target
entrypoint to the fuzzing infrastructure:
// fuzz/fuzz_targets/target1.rs file
#![no_main]
use libfuzzer_sys::fuzz_target;
fuzz_target!(|data: &[u8]| {
let _ = somecrate::is_fuzz(data);
});
Running cargo +nightly fuzz run target1 continuously executes the fuzz target
with random data, stopping only if a crash is found. In this case, a failure is found
almost immediately:
INFO: Running with entropic power schedule (0xFF, 100).
INFO: Seed: 1607525774
232 | Chapter 5: Tooling
INFO: Loaded 1 modules: 1624 [0x108219fa0, 0x10821a5f8),
INFO: Loaded 1 PC tables (1624 PCs): 1624 [0x10821a5f8,0x108220b78),
INFO: 9 files found in fuzz/corpus/target1
INFO: seed corpus: files: 9 min: 1b max: 8b total: 46b rss: 38Mb
#10
INITED cov: 26 ft: 26 corp: 6/22b exec/s: 0 rss: 39Mb
thread panicked at 'index out of bounds: the len is 3 but the index is 3',
testing/src/lib.rs:77:12
stack backtrace:
0: rust_begin_unwind
at /rustc/f77bfb7336f2/library/std/src/panicking.rs:579:5
1: core::panicking::panic_fmt
at /rustc/f77bfb7336f2/library/core/src/panicking.rs:64:14
2: core::panicking::panic_bounds_check
at /rustc/f77bfb7336f2/library/core/src/panicking.rs:159:5
3: somecrate::is_fuzz
4: _rust_fuzzer_test_input
5: ___rust_try
6: _LLVMFuzzerTestOneInput
7: __ZN6fuzzer6Fuzzer15ExecuteCallbackEPKhm
8: __ZN6fuzzer6Fuzzer6RunOneEPKhmbPNS_9InputInfoEbPb
9: __ZN6fuzzer6Fuzzer16MutateAndTestOneEv
10: __ZN6fuzzer6Fuzzer4LoopERNSt3__16vectorINS_9SizedFileENS_
16fuzzer_allocatorIS3_EEEE
11: __ZN6fuzzer12FuzzerDriverEPiPPPcPFiPKhmE
12: _main
and the input that triggered the failure is emitted.
Normally, fuzz testing does not find failures so quickly, and so it does not make sense
to run fuzz tests as part of your CI. The open-ended nature of the testing, and the
consequent compute costs, mean that you need to consider how and when to run
fuzz tests—perhaps only for new releases or major changes, or perhaps for a limited
You can also make subsequent runs of the fuzzing infrastructure more efficient, by
storing and reusing a corpus of previous inputs that the fuzzer found to explore new
code paths; this helps subsequent runs of the fuzzer explore new ground, rather than
retesting code paths previously visited.
6 If your code is a widely used open source cra may be willing to run fuzzing on your behalf.
Item 30: Write more than unit tests | 233
Testing Advice
An Item about testing wouldn’t be complete without repeating some common advice
(which is mostly not Rust-specific):
• As this Item has endlessly repeated, run all your tests in CI on every change (with
the exception of fuzz tests).
• When you’re fixing a bug, write a test that exhibits the bug before fixing the bug.
That way you can be sure that the bug is fixed and that it won’t be accidentally
reintroduced in the future.
• If your crate has fea run tests over every possible combination of available features.
• More generally, if your crate includes any config-specific code (e.g., #[cfg(tar
get_os = "windows")]), run tests for every platform that has distinct code.
This Item has covered a lot of different types of tests, so it’s up to you to decide how
much each of them is relevant and worthwhile for your project.
If you have a lot of test code and you are publishing your cra
you might need to consider which of the tests make sense to include in the published
crate. By default, cargo will include unit tests, integration tests, benchmarks, and
examples (but not fuzz tests, because the cargo-fuzz tools store these as a separate
crate in a subdirectory), which may be more than end users need. If that’s the case,
you can either some of the files or (for behavior tests) move the tests out of the crate and into a separate test crate.
Things to Remember
• Write unit tests for comprehensive testing that includes testing of internal-only
code. Run them with cargo test.
• Write integration tests to exercise your public API. Run them with cargo test.
• Write doc tests that exemplify how to use individual items in your public API.
Run them with cargo test.
• Write example programs that show how to use your public API as a whole. Run
them with cargo test --examples or cargo run --example <name>.
• Write benchmarks if your code has significant performance requirements. Run
them with cargo bench.
• Write fuzz tests if your code is exposed to untrusted inputs. Run them (continu‐
ously) with cargo fuzz.
234 | Chapter 5: Tooling
Item 31: Take advantage of the tooling ecosystem
The Rust ecosystem has a rich collection of additional tools, which provide function‐
ality above and beyond the essential task of converting Rust into machine code.
When setting up a Rust development environment, you’re likely to want most of the
following basic tools:
• piler
• tool, which manages the installed Rust toolchains
• An IDE with Rust support, or an IDE/editor plug-in like t allows you to quickly navigate around a Rust codebase, and provides autocom-pletion support for writing Rust code
• , for standalone explorations of Rust’s syntax and for sharing the results with colleagues
•
Beyond these basics, Rust includes many tools that help with the wider task of main‐
taining a codebase and improving the quality of that codebase. The
in the official Cargo toolchain cover various essential tasks beyond the basics of cargo build, cargo test, and cargo run, for example:
cargo fmt
Reformats Rust code according to standard conventions.
cargo check
Performs compilation checks without generating machine code, which can be
useful to get a fast syntax check.
cargo clippy
Performs lint checks, detecting inefficient or unidiomatic code ().
cargo doc
Generates documentation (
cargo bench
R).
7 This list may be reduced in some environments. For examtrally controlled toolchain (so no rustup) and integrates with Android’s Soong build system (so no cargo).
Item 31: Take advantage of the tooling ecosystem | 235
cargo update
Upgrades dependencies to the latest versions, selecting versions that are compli‐
ant with semantic versioning (ult.
cargo tree
Displays the dependency graph (
cargo metadata
Emits metadata about the packages that are present in the workspace and in their
dependencies.
The last of these is particularly useful, albeit indirectly: because there’s a tool that
emits information about crates in a well-defined format, it’s much easier for people to
produce other tools that make use of that informa
te, which provides a set of Rust types to hold the metadata information).
described some of the tools that are enabled by this metadata availability,
such as cargo-udeps (which allows detection of unused dependencies) or cargo-
deny (which allows checks for many things, including duplicate dependencies,
allowed licenses, and security advisories).
The extensibility of the Rust toolchain is not just limited to package metadata; the
compiler’s abstract syncrate. This information is wha) so potent but also powers a
variety of other tools:
Shows the complete source code produced by macro expansion, which can be
essential for debugging tricky macro definitions.
Supports the generation and tracking of code coverage information.
Any list of specific tools will always be subjective, out of date, and incomplete; the
more general point is to explore the available tools.
For exam gives dozens of results; some will be inappropriate and some will be abandoned, but some might just do exactly what
you want.
y be helpful if your code needs higher levels of assurance about its correctness.
Finally, a reminder: if a tool is useful on more than a one-off basis, you should inte‐
grate the tool into your CI system (as per ). If the tool is fast and false-positive free, it may also make sense to integrate the tool into your editor or IDE; the Rust
provides links to relevant documentation for this.
236 | Chapter 5: Tooling
Tools to Remember
In addition to the tools that should be configured to run over your codebase regularly
and automatically (), there are various other tools that have been mentioned
elsewhere in the book. For reference, these are collated here—but remember that
there are many more tools out there:
• unsafe code.
• I, for managing dependency updates.
•
that semantic versioning has been done correctly.
•
• tirely dedicated to the use of Clippy.
• corresponding to your source code, as described in
• also men
• covers the use of for auto-generating Rust FFI wrappers from C
code.
Item 32: Set up a continuous integration (CI) system
A CI system is a mechanism for automatically running tools over your codebase,
which is triggered whenever there’s a change to the codebase—or a proposed change
to the codebase.
The recommendation to set up a CI system is not at all Rust-specific, so this Item is a
mélange of general advice mixed with Rust-specific tool suggestions.
CI Steps
Moving to specifics, what kinds of steps should be included in your CI system? The
obvious initial candidates are the following:
• Build the code.
• Run the tests for the code.
In each case, a CI step should run cleanly, quickly, deterministically, and with a zero
false positive rate; more on this in the next section.
Item 32: Set up a continuous integration (CI) system | 237
The “deterministic” requirement also leads to advice for the build step: use rust-
toolchain.toml to specify a fixed version of the toolchain in your CI build.
file indicates which version of Rust should be used to build the code—either a specific version (e.g., 1.70), or a channel (stable, beta, or
nightly) possibly with an optional date (e.g., nightly-2023-09-19 Choosing a
floating channel value here would make the CI results vary as new toolchain versions
are released; a fixed value is more deterministic and allows you to deal with toolchain
upgrades separately.
Throughout this book, various Items have suggested tools and techniques that can
help improve your codebase; wherever possible, these should be included with the CI
system. For example, the two fundamental parts of a CI system previously mentioned
can be enhanced:
• Build the code.
— describes the use of features to conditionally include different chunks of code. If your crate has features, build every valid combination of features in
CI (and realize that this may involve 2N different variants—hence the advice to
avoid feature creep).
— t you consider making library code no_std compatible
where possible. You can be confident that your code is genuinely no_std com‐
patible only if you test no_std compatibility in CI. One option is to make use
of the Rust compiler’s cross-compilation abilities and build for an explicitly
no_std target (e.g., thumbv6m-none-eabi).
— includes a discussion around declaring a minimum supported Rust
version (MSRV) for your code. If you have this, check your MSRV in CI by
including a step that tests with that specific Rust version.
• Run the tests for the code.
— describes the various different styles of test; run all test types in CI.
Some test types are automatically included in cargo test (unit tests, integra‐
tion tests, and doc tests), but other test types (e.g., example programs) may
need to be explicitly triggered.
However, there are other tools and suggestions that can help improve the quality of
your codebase:
8 If your code relies on particular features that are available only in the nightly compiler, a rust-toolchain.toml file also makes that toolchain dependency clear.
238 | Chapter 5: Tooling
• waxes lyrical about the advantages of running Clippy over your code; run Clippy in CI. To ensure that failures are flagged, set the -Dwarnings option (for
example, via cargo clippy -- -Dwarnings).
• ting your public API; use the cargo doc tool to check
that the documentation generates correctly and that any hyperlinks in it resolve
correctly.
• mentions tools such as cargo-udeps and cargo-deny that can help man‐
age your dependency graph; running these as a CI step prevents regressions.
• discusses the Rust tool ecosystem; consider which of these tools are
worth regularly running over your codebase. For example, running rustfmt /
cargo fmt in CI allows detection of code that doesn’t comply with your project’s
style guidelines. To ensure that failures are flagged, set the --check option.
You can also include CI steps that measure particular aspects of your code:
• Generate code coverage stat proportion of your codebase is exercised by your tests.
• Run benchmarks (e.g., with cargo-bench) to measure the performance
of your code on key scenarios. However, note that most CI systems run in shared
environments where external factors can affect the results; getting more reliable
benchmark data is likely to require a more dedicated environment.
These measurement suggestions are a bit more complicated to set up, because the
output of a measurement step is more useful when it’s compared to previous results.
In an ideal world, the CI system would detect when a code change is not fully tested
or has an adverse effect on performance; this typically involves integration with some
external tracking system.
Here are other suggestions for CI steps that may or may not be relevant for your
codebase:
• If your project is a library, recall (from ) that any checked-in Cargo.lock file will be ignored by the users of your library. In theory, the semantic version
constrain) in Cargo.toml should mean that everything works correctly anyway; in practice, consider including a CI step that builds without any local
Cargo.lock, to detect whether the current versions of dependencies still work cor‐
rectly.
• If your project includes any kind of machine-generated resources that are
version-controlled (e.g., code generated from protocol buffer messages by
), then include a CI step that regenerates the resources and checks that there are no differences compared to the checked-in version.
Item 32: Set up a continuous integration (CI) system | 239
• If your codebase includes platform-specific (e.g., #[cfg(target_arch =
"arm")]) code, run CI steps that confirm that the code builds and (ideally) works
on that platform. (The former is easier than the latter because the Rust toolchain
includes support for cross-compilation.)
• If your project manipulates secret values such as access tokens or cryptographic
keys, consider including a CI step that searches the codebase for secrets that have
been inadvertently checked in. This is particularly important if your project is
public (in which case it may be worth moving the check from CI to a
CI checks don’t always need to be integrated with Cargo and the Rust toolchains;
sometimes a simple shell script can give more bang for the buck, particularly when a
codebase has a local convention that’s not universally followed. For example, a code‐
base might include a convention that any panic-inducing method invocation (
) has a special marker comment or that every TODO: comment has an owner (a per‐
son or a tracking ID), and a shell script is ideal for checking this.
Finally, consider examining the CI systems of public Rust projects to get ideas for
additional CI steps that might be useful for your project. For exam
that includes many steps that may provide inspiration.
CI Principles
Moving from the specific to the general, there are some overall principles that should
guide the details of your CI system.
The most fundamental principle is don’t waste the time of humans. If a CI system
unnecessarily wastes people’s time, they will start looking for ways to avoid it.
The most annoying waste of an engineer’s time is a flaky test: sometimes it passes and
sometimes it fails, even when the setup and codebase are identical. Whenever possi‐
ble, be ruthless with flaky tests: hunt them down, and put in the time up front to
investigate and fix the cause of the flakiness—it will pay for itself in the long run.
Another common waste of engineering time is a CI system that takes a long time to
run and that runs only after a request for a code review has been triggered. In this
situation, there’s the potential to waste two people’s time: both the author and also the
code reviewer, who may spend time spotting and pointing out issues with the code
that the CI bots could have flagged.
To help with this, try to make it easy to run the CI checks manually, independent
from the automated system. This allows engineers to get into the habit of triggering
them regularly so that code reviewers never even see problems that the CI would have
flagged. Better still, make the integration even more continuous by incorporating
240 | Chapter 5: Tooling
some of the tools into your editor or IDE setup so that (for example) poorly format‐
ted code never even makes it to disk.
This may also require splitting the checks up if there are time-consuming tests that
rarely find problems but are there as a backstop to prevent obscure scenarios
breaking.
More generally, a large project may need to divide up its CI checks according to the
cadence at which they are run:
• Checks that are integrated into each engineer’s development environment (e.g.,
rustfmt)
• Checks that run on every code review request (e.g., cargo build, cargo clippy)
and are easy to run manually
• Checks that run on every change that makes it to the main branch of the project
(e.g., full cargo test in all supported environments)
• Checks that run at scheduled intervals (e.g., daily or weekly), which can catch
rare regressions after the fact (e.g., long-running integration tests and benchmark
comparison tests)
• Checks that run on the current code at all times (e.g., fuzz tests)
It’s important that the CI system be integrated with whatever code review system is
used for your project so that a code review can clearly see a green set of checks and be
confident that its code review can focus on the important meaning of the code, not on
trivial details.
This need for a green build also means that there can be no exceptions to whatever
checks your CI system has put in place. This is worthwhile even if you have to work
around an occasional false positive from a tool; once your CI system has an accepted
failure (“Oh, everyone knows that test never passes”), then it’s vastly harder to spot
new regressions.
ing the bug. The same principle applies to your CI system: when you discover process
problems add a CI step that detects a process issue, before fixing the issue. For example,
if you discover that some auto-generated code has gotten out of sync with its source,
add a check for this to the CI system. This check will initially fail but then turn green
once the problem is solved—giving you confidence that this category of process error
will not occur again in the future.
Item 32: Set up a continuous integration (CI) system | 241
Public CI Systems
If your codebase is open source and visible to the public, there are a few extra things
to think about with your CI system.
First is the good news: there are lots of free, reliable options for building a CI system
for open source code. At the time of writing, are probably the best choice, but it’s far from the only choice, and more systems appear all the time.
Second, for open source code it’s worth bearing in mind that your CI system can act
as a guide for how to set up any prerequisites needed for the codebase. This isn’t a
concern for pure Rust crates, but if your codebase requires additional dependencies—
databases, alternative toolchains for FFI code, configuration, etc.—then your CI
scripts will be an existence proof of how to get all of that working on a fresh system.
Encoding these setup steps in reusable scripts allows both the humans and the bots to
get a working system in a straightforward way.
Finally, there’s bad news for publicly visible crates: the possibility of abuse and attacks.
This can range from attempts to perform cryptocurrency mining in your CI system
ttacks, and worse. To mitigate these risks, consider these guidelines:
• Restrict access so that CI scripts run automatically only for known collaborators
and have to be triggered manually for new contributors.
• Pin the versions of any external scripts to particular versions, or (better yet) spe‐
cific known hashes.
• Closely monitor any integration steps that need more than just read access to the
codebase.
242 | Chapter 5: Tooling
CHAPTER 6
Beyond Standard Rust
The Rust toolchain includes support for a much wider variety of environments than
just pure Rust application code, running in userspace:
• It supports cross-compilation, where the system running the toolchain (the host)
is not the same as the system that the compiled code will run on (the target),
which makes it easy to target embedded systems.
• It supports linking with code compiled from languages other than Rust, via built-
in FFI capabilities.
• It supports configurations without the full standard library std, allowing systems
that do not have a full operating system (e.g., no filesystem, no networking) to be
targeted.
• It even supports configurations that do not support heap allocation but only have
a stack (by omitting use of the standard alloc library).
These nonstandard Rust environments can be harder to work in and may be less
safe—they can even be unsafe—but they give more options for getting the job done.
This chapter of the book discusses just a few of the basics for working in these envi‐
ronments. Beyond these basics, you’ll need to consult more environment-specific
documentation (such as the ).
Item 33: Consider making library code
no_std compatible
Rust comes with a standard library called std, which includes code for a wide variety
of common tasks, from standard data structures to networking, from multithreading
support to file I/O. For convenience, several of the items from std are automatically
243
imported into your program, via the : a set of common use statements that make common types available without needing to use their full names (e.g., Vec
rather than std::vec::Vec).
Rust also supports building code for environments where it’s not possible to provide
this full standard library, such as bootloaders, firmware, or embedded platforms in
general. Crates indicate that they should be built in this way by including the
#![no_std] crate-level attribute at the top of src/lib.rs.
This Item explores what’s lost when building for no_std and what library functions
you can still rely on—which turns out to be quite a lot.
However, this Item is specifically about no_std support in library code. The difficul‐
ties of making a no_std binary are beyond this text, so the focus here is how to make sure that library code is available for those poor souls who do have to work in such a
minimal environment.
core
Even when building for the most restricted of platforms, many of the fundamental
types from the standard library are still available. For exam
are still available, albeit under a different name, as are various flavors of
The different names for these fundamental types start with core::, indicating that
they come from the core library, a standard library that’s available even in the most
no_std of environments. These core:: types behave exactly the same as the equiva‐
lent std:: types, because they’re actually the same types—in each case, the std:: ver‐
sion is just a re-export of the underlying core:: type.
This means that there’s a quick and dirty way to tell if a std:: item is available in a
no_std environmen page for the std item you’re interested in and follow the “source” link (at the top righ If that takes you to a src/
core/… location, then the item is available under no_std via core::.
The types from core are available for all Rust programs automatically. However, they
typically need to be explicitly used in a no_std environment, because the std prelude
is absent.
1 See or Philipp Oppermann’tion about what’s involved in creating a no_std binary.
2 Be aware that this can occasionally go wrong. For example, at the time of writing, the Error trait is defined in
is stable.
244 | Chapter 6: Beyond Standard Rust
In practice, relying purely on core is too limiting for many environments, even
no_std ones. A core (pun intended) constraint of core is that it performs no heap
al ocation.
Although Rust excels at putting items on the stack and safely tracking the corre‐
t standard data
structures—vectors, maps, sets—can’t be provided, because they need to allocate heap
space for their contents. In turn, this also drastically reduces the number of available
crates that work in this environment.
alloc
However, if a no_std environment does support heap allocation, then many of the
standard data structures from std can still be supported. These data structures, along
with other allocation-using functionality, are grouped into Rust’y.
As with core, these alloc variants are actually the same types under the covers. For
exam.
A no_std Rust crate needs to explicitly opt in to the use of alloc, by adding an
extern crate alloc; declaration to src/lib.rs
//! My `no_std` compatible crate.
#![no_std]
// Requires àlloc`.
extern crate alloc;
Pulling in the alloc crate enables many familiar friends, now addressed by their true
names:
•
•
•
•
•
•
•
•
With these things available, it becomes possible for many library crates to be no_std
compatible—for example, if a library doesn’t involve I/O or networking.
3 Prior to Rust 2018, extern crate declarations were used to pull in dependencies. This is now entirely handled by Cargo.toml, but the extern crate mechanism is still used to pull in those parts of the Rust standard librart are optional in no_std environments.
Item 33: Consider making library code no_std compatible | 245
There’s a notable absence from the data structures that alloc makes available,
though— are specific to std, not alloc. That’s because these hash-based containers rely on random seeds to protect against hash
collision attacks, but safe random number generation requires assistance from the
operating system—which alloc can’t assume exists.
Another notable absence is synchronization functionality like , which is required for multithreaded code (). These types are specific to std
because they rely on OS-specific synchronization primitives, which aren’t available
without an OS. If you need to write code that is both no_std and multithreaded,
third-party crates such as
Writing Code for no_std
The previous sections made it clear that for some library crates, making the code
no_std compatible just involves the following:
• Replacing std:: types with identical core:: or alloc:: crates (which requires
use of the full type name, due to the absence of the std prelude)
• Shifting from HashMap/HashSet to BTreeMap/BTreeSet
However, this only makes sense if all of the crates tha) are
also no_std compatible—there’s no point in becoming no_std compatible if any user
of your crate is forced to link in std anyway.
There’s also a catch here: the Rust compiler will not tell you if your no_std crate
depends on a std-using dependency. This means that it’s easy to undo the work of
making a crate no_std compatible—all it takes is an added or updated dependency
that pulls in std.
To protect against this, add a CI check for a no_std build so that your CI system (
) will warn you if this happens. The Rust toolchain supports cross-compilation out
of the box, so this can be as sim for a target system that does not support std (e.g., --target thumbv6m-none-eabi); any code that inadvertently requires std will then fail to compile for this target.
So: if your dependencies support it, and the simple transformations above are all
that’s needed, then consider making library code no_std compatible. When it is possi‐
ble, it’s not much additional work, and it allows for the widest reuse of the library.
If those transformations don’t cover all of the code in your crate but the parts that
aren’t covered are only a small or well-contained fraction of the code, then consider
adding a feature () to your crate that turns on just those parts.
246 | Chapter 6: Beyond Standard Rust
Such a feature is conventionally named either std, if it enables use of std-specific
functionality:
#![cfg_attr(not(feature = "std"), no_std)]
or alloc, if it turns on use of alloc-derived functionality:
#[cfg(feature = "alloc")]
extern crate alloc;
Note that there’s a trap for the unwary here: don’t have a no_std feature that disables
functionality requiring std (or a no_alloc feature similarly). As explained in , features need to be additive, and there’s no way to combine two users of the crate
where one configures no_std and one doesn’t—the former will trigger the removal of
code that the latter relies on.
As ever with feature-gated code, make sure that your CI system () builds all
the relevant combinations—including a build with the std feature disabled on an
explicitly no_std platform.
Fallible Allocation
The earlier sections of this Item considered two different no_std environments: a
fully embedded environment with no heap allocation whatsoever (core) and a more
generous environment where heap allocation is allowed (core + alloc).
However, there are some important environments that fall between these two
camps— in particular, those where heap allocation is possible but may fail because
there’s a limited amount of heap.
Unfortunately, Rust’s standard alloc library includes a pervasive assumption that
heap allocations cannot fail, and that’s not always a valid assumption.
Even a simple use of alloc::vec::Vec could potentially allocate on every line:
let mut v = Vec::new();
v.push(1); // might allocate
v.push(2); // might allocate
v.push(3); // might allocate
v.push(4); // might allocate
None of these operations returns a Result, so what happens if those allocations fail?
The answer depends on the toolchain, target, and but is likely to descend into panic! and program termination. There is certainly no answer that
allows an allocation failure on line 3 to be handled in a way that allows the program
to move on to line 4.
Item 33: Consider making library code no_std compatible | 247
This assumption of infal ible al ocation gives good ergonomics for code that runs in a
“normal” userspace, where there’s effectively infinite memory—or at least where run‐
ning out of memory indicates that the computer as a whole has bigger problems
elsewhere.
However, infallible allocation is utterly unsuitable for code that needs to run in envi‐
ronments where memory is limited and programs are required to cope. This is a
(rare) area where there’s better support in older, less memory-safe, languages:
• C is sufficiently low-level that allocations are manual, and so the return value
from malloc can be checked for NULL.
• C++ can use its exception mechanism to catch allocation failures in the form of
Historically, the inability of Rust’s standard library to cope with failed allocation was
flagged in some high-profile contexts (such as the , Android, and the
), and so work to fix the omission is ongoing.
alternatives to many of the collection APIs that involve allocation. This generally adds
a try_<operation> variant that results in a Result<_, AllocError>; for example:
• vailable as an alternative to
• vailable (with the nightly toolchain) as an alternative to
These fallible APIs only go so far; for example, there is (as yet) no fallible equivalent
t assembles a vector may need to do careful calculations to ensure that allocation errors can’t happen:
fn try_build_a_vec() -> Result<Vec< u8>, String> {
let mut v = Vec::new();
// Perform a careful calculation to figure out how much space is needed,
// here simplified to...
let required_size = 4;
v.try_reserve(required_size)
.map_err(|_e| format!("Failed to allocate {} items!", required_size))?;
// We now know that it's safe to do:
4 It’s also possible to add the overload to calls to new and check for nullptr return values. However, there are still con that allocate under the covers and that can therefore signal allocation failure only via an exception.
248 | Chapter 6: Beyond Standard Rust
v.push(1);
v.push(2);
v.push(3);
v.push(4);
Ok(v)
}
As well as adding fallible allocation entrypoints, it’s also possible to disable infal ible
allocation operations, by turning off the config flag (which is on by default). Environments with limited heap (such as the Linux kernel) can
explicitly disable this flag, ensuring that no use of infallible allocation can inadver‐
tently creep into the code.
Things to Remember
• Many items in the std crate actually come from core or alloc.
• As a result, making library code no_std compatible may be more straightforward
than you might think.
• Confirm that no_std code remains no_std compatible by checking it in CI.
• Be aware that working in a limited-heap environment currently has limited
library support.
Item 34: Control what crosses FFI boundaries
Even though Rust comes with a comprehensive and a burgeoning
ust code in the world than there is Rust code.
As with other recent languages, Rust helps with this problem by offering a foreign
function interface (FFI) mechanism, which allows interoperation with code and data
structures written in different languages—despite the name, FFI is not restricted to
just functions. This opens up the use of existing libraries in different languages, not
just those that have succumbed to the Rust community’s efforts to “rewrite it in Rust”
(RiiR).
The default target for Rust’s interoperability is the C programming language, which is
the same interop target that other languages aim at. This is partly driven by the ubiq‐
uity of C libraries but is also driven by simplicity: C acts as a “least common denomi‐
nator” of interoperability, because it doesn’t need toolchain support of any of the
more advanced features that would be necessary for compatibility with other lan‐
guages (e.g., garbage collection for Java or Go, exceptions and templates for C++,
function overrides for Java and C++, etc.).
Item 34: Control what crosses FFI boundaries | 249
However, that’s not to say that interoperability with plain C is simple. By including
code written in a different language, all of the guarantees and protections that Rust
offers are up for grabs, particularly those involving memory safety.
As a result, FFI code in Rust is automatically unsafe has to be bypassed. This Item explores some replacemen
some tooling that helps to avoid some (but not all) of the footguns involved in work‐
ing with FFI. (The of the also contains helpful advice and information.)
Invoking C Functions from Rust
The simplest FFI interaction is for Rust code to invoke a C function, taking “immedi‐
ate” arguments that don’t involve pointers, references, or memory addresses:
/* File lib.c */
#include "lib.h"
/* C function definition. */
int add(int x, int y) {
return x + y;
}
This C code provides a definition of the function and is typically accompanied by a
header file that provides a declaration of the function, which allows other C code to
use it:
/* File lib.h */
#ifndef LIB_H
#define LIB_H
/* C function declaration. */
int add(int x, int y);
#endif /* LIB_H */
The declaration roughly says: somewhere out there is a function called add, which
takes two integers as input and returns another integer as output. This allows C code
to use the add function, subject to a promise that the actual code for add will be pro‐
vided at a later date—specifically, at link time.
Rust code that wants to use add needs to have a similar declaration, with a similar
purpose: to describe the signature of the function and to indicate that the corre‐
sponding code will be available later:
use std::os::raw::c_int;
extern "C" {
pub fn add(x: c_int, y: c_int) -> c_int;
}
250 | Chapter 6: Beyond Standard Rust
The declaration is marked as extern "C" to indicate that an external C library will
extern "C" marker also automatically marks
Linking logistics
The details of how the C toolchain generates an external C library—and its format—
are environment-specific and beyond the scope of a Rust book like this. However, one
simple variant that’s common on Unix-like systems is a static library file, which will
normally have the form lib<something>.a (e.g., libcffi.a) and which can be generated
tool.
The Rust build system then needs an indication of which library holds the relevant C
in the code:
#[link(name = "cffi")] // An external library likèlibcffi.aìs needed
extern "C" {
// ...
}
or via a cargo
// File build.rs
fn main() {
// An external library likèlibcffi.aìs needed
println!("cargo:rustc-link-lib=cffi");
}
The latter option is more flexible, because the build script can examine its environ‐
ment and behave differently depending on what it finds.
In either case, the Rust build system is also likely to need information about how to
find the C library, if it’s not in a standard system location. This can be specified by
having a build script tha instruction to cargo, containing the library location:
// File build.rs
fn main() {
// ...
// Retrieve the location of `Cargo.toml`.
let dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
// Look for native libraries one directory higher up.
println!(
"cargo:rustc-link-search=native={}",
5 If the FFI functionality you want to use is part of the standard C library, then you don’t need to create these declara crate already provides them.
6 Cargo.toml manifest can help to make this dependency visible to Cargo.
Item 34: Control what crosses FFI boundaries | 251
std::path::Path::new(&dir).join("..").display()
);
}
Code concerns
Returning to the source code, even this simplest of examples comes with some
gotchas. First, use of FFI functions is automatically unsafe:
let x = add(1, 1);
error[E0133]: call to unsafe function is unsafe and requires unsafe function
or block
--> src/main.rs:176:13
|
176 | let x = add(1, 1);
| ^^^^^^^^^ call to unsafe function
|
= note: consult the function's documentation for information on how to
avoid undefined behavior
and so needs to be wrapped in unsafe { }.
The next thing to watch out for is the use of C’s int type, represented as
. How big is an int? It’s probably true that the following two things are the same:
• The size of an int for the toolchain that compiled the C library
• The size of a std::os::raw::c_int for the Rust toolchain
But why take the chance? Prefer sized types at FFI boundaries, where possible—which
for C means making use of the types (e.g., uint32_t) defined in <stdint.h>. How‐
ever, if you’re dealing with an existing codebase that already uses int/long/size_t,
this may be a luxury you don’t have.
The final practical concern is that the C code and the equivalent Rust declaration
need to exactly match. Worse still, if there’s a mismatch, the build tools will not emit a
warning—they will just silently emit incorrect code.
discusses the use of the bindgen tool to prevent this problem, but it’s worth
understanding the basics of what’s going on under the covers to understand why the
build tools can’t detect the problem on their own. In particular, it’s worth understand‐
ing the basics of name mangling.
252 | Chapter 6: Beyond Standard Rust
Name mangling
Compiled languages generally support separate compilation, where different parts of
the program are converted into machine code as separate chunks (object files), which
can then be combined into a complete program by the linker. This means that if only
one small part of the program’s source code changes, only the corresponding object
file needs to be regenerated; the link step then rebuilds the program, combining both
the changed object and all the other unmodified objects.
The
vide definitions of functions and variables, and other object files have placeholder
markers indicating that they expect to use a definition from some other object, but it
wasn’t available at compile time. The linker combines the two: it ensures that any
placeholder in the compiled code is replaced with a reference to the corresponding
concrete definition.
The linker performs this correlation between the placeholders and the definitions by
simply checking for a matching name, meaning that there is a single global name‐
space for all of these correlations.
Historically, this was fine for linking C language programs, where a single name
could not be reused in any way—the name of a function is exactly what appears in the
object file. (As a result, a common convention for C libraries is to manually add a
prefix to all symbols so that lib1_process doesn’t clash with lib2_process.)
However, the introduction of C++ caused a problem because C++ allows overridden
definitions with the same name:
// C++ code
namespace ns1 {
int32_t add(int32_t a, int32_t b) { return a+b; }
int64_t add(int64_t a, int64_t b) { return a+b; }
}
namespace ns2 {
int32_t add(int32_t a, int32_t b) { return a+b; }
}
The solution for this is name mangling: the
to the name that’s emitted in the object file, and the linker continues to perform its simple-minded 1:1 correlation between
placeholders and definitions.
On Unix-like systems, the t the linker works with:
% nm ffi-lib.o | grep add # what the linker sees for C
0000000000000000 T _add
% nm ffi-cpp-lib.o | grep add # what the linker sees for C++
Item 34: Control what crosses FFI boundaries | 253
0000000000000000 T __ZN3ns13addEii
0000000000000020 T __ZN3ns13addExx
0000000000000040 T __ZN3ns23addEii
In this case, it shows three mangled symbols, all of which refer to code (the T indi‐
cates the text section of the binary, which is the traditional name for where code lives).
te this back into what would be visible in C++ code:
% nm ffi-cpp-lib.o | grep add | c++filt # what the programmer sees
0000000000000000 T ns1::add(int, int)
0000000000000020 T ns1::add(long long, long long)
0000000000000040 T ns2::add(int, int)
Because the mangled name includes type information, the linker can and will com‐
plain about any mismatch in the type information between placeholder and defini‐
tion. This gives some measure of type safety: if the definition changes but the place
using it is not updated, the toolchain will complain.
Returning to Rust, extern "C" foreign functions are implicitly marked as #[no_man
gle], and the symbol in the object file is the bare name, exactly as it would be for a C
program. This means that the type safety of function signatures is lost: because the
linker sees only the bare names for functions, if there are any differences in type
expectations between definition and use, the linker will carry on regardless and prob‐
lems will arise only at runtime.
Accessing C Data from Rust
The C add example in the previous section passed the simplest possible type of data
back and forth between Rust and C: an integer that fits in a machine register. Even so,
there were still things to be careful about, so it’s no surprise then that dealing with
more complex data structures also has wrinkles to watch out for.
Both C and Rust use the struct to combine related data into a single data structure.
However, when a struct is realized in memory, the two languages may well choose to
put different fields in different places or even in different orders (the To prevent mismatches, use #[repr(C)] for Rust types used in FFI; this
/* C data structure definition. */
/* Changes here must be reflected in lib.rs. */
typedef struct {
uint8_t byte;
uint32_t integer;
} FfiStruct;
// Equivalent Rust data structure.
// Changes here must be reflected in lib.h / lib.c.
#[repr(C)]
pub struct FfiStruct {
254 | Chapter 6: Beyond Standard Rust
pub byte: u8,
pub integer: u32,
}
The structure definitions have a comment to remind the humans involved that the
two places need to be kept in sync. Relying on the constant vigilance of humans is
likely to go wrong in the long term; as for function signatures, it’s better to automate
this synchronization between the two languages via a tool like bindgen).
One particular type of data that’s worth thinking about carefully for FFI interactions
is strings. The default definitions of what makes up a string are somewhat different
between C and Rust:
• A Rust holds UTF-8 encoded data, possibly including zero bytes, with an explicitly known length.
• A C string (char *) holds byte values (which may or may not be signed), with its
length implicitly determined by the first zero byte (\0) found in the data.
Fortunately, dealing with C-style strings in Rust is comparatively straightforward,
because the Rust library designers have already done the heavy lifting by providing a
pair of types to encode them. Use the type to hold (owned) strings that need to be interoperable with C, and use the corresponding
borrowed string values. The latter type includes the method, which can be used to pass the string’s contents to any FFI function that’s expecting a const char* C
string. Note that the const is important: this can’t be used for an FFI function that
needs to modify the contents (char *) of the string that’s passed to it.
Lifetimes
Most data structures are too big to fit in a register and so have to be held in memory
instead. That in turn means that access to the data is performed via the location of
that memory. In C terms, this means a pointer: a number that encodes a memory
address—with no other semantics a
In Rust, a location in memory is generally represented as a reference, and its numeric
value can be extracted as a raw pointer, ready to feed into an FFI boundary:
extern "C" {
// C function that does some operation on the contents
// of an `FfiStruct`.
pub fn use_struct(v: *const FfiStruct) -> u32;
}
let v = FfiStruct {
byte: 1,
integer: 42,
Item 34: Control what crosses FFI boundaries | 255
};
let x = unsafe { use_struct(&v as *const FfiStruct) };
However, a Rust reference comes with additional constraints around the lifetime of
the associated chunk of memoryts get lost in
the conversion to a raw pointer.
As a result, the use of raw pointers is inherently unsafe, as a marker that Here Be
Dragons: the C code on the other side of the FFI boundary could do any number of
things that will destroy Rust’s memory safety:
• The C code could hang onto the value of the pointer and use it at a later point
when the associated memory has either been freed from the heap or reused on
the stack ( use-after-free).
• The C code could decide to cast away the const-ness of a pointer that’s passed to
it and modify data that Rust expects to be immutable.
• The C code is not subject to Rust’s Mutex protections, so the specter of data races
) rears its ugly head.
• The C code could mistakenly return associated heap memory to the allocator (by
calling C’s free() library function), meaning that the Rust code might now be
performing use-after-free operations.
All of these dangers form part of the cost-benefit analysis of using an existing library
via FFI. On the plus side, you get to reuse existing code that’s (presumably) in good
working order, with only the need to write (or auto-generate) corresponding declara‐
tions. On the minus side, you lose the memory protections that are a big reason to
use Rust in the first place.
As a first step to reduce the chances of memory-related problems, al ocate and free
memory on the same side of the FFI boundary. For example, this might appear as a
symmetric pair of functions:
/* C functions. */
/* Allocate an `FfiStruct` */
FfiStruct* new_struct(uint32_t v);
/* Free a previously allocated `FfiStruct` */
void free_struct(FfiStruct* s);
with corresponding Rust FFI declarations:
extern "C" {
// C code to allocate an `FfiStruct`.
pub fn new_struct(v: u32) -> *mut FfiStruct;
// C code to free a previously allocated `FfiStruct`.
pub fn free_struct(s: *mut FfiStruct);
}
256 | Chapter 6: Beyond Standard Rust
To make sure that allocation and freeing are kept in sync, it can be a good idea to
implement an RAII wrapper that automatically prevents C-allocated memory from
being leaked (pper structure owns the C-allocated memory:
/// Wrapper structure that owns memory allocated by the C library.
struct FfiWrapper {
// Invariant: inner is non-NULL.
inner: *mut FfiStruct,
}
and the Drop implementation returns that memory to the C library to avoid the
potential for leaks:
/// Manual implementation of [`Drop`], which ensures that memory allocated
/// by the C library is freed by it.
impl Drop for FfiWrapper {
fn drop(& mut self) {
// Safety: ìnnerìs non-NULL, and besides `free_struct()` copes
// with NULL pointers.
unsafe { free_struct(self.inner) }
}
}
The same principle applies to more than just heap memory: implement Drop to apply
RAII to FFI-derived resources—open files, database connections, etc. (see ).
Encapsulating the interactions with the C library into a wrapper struct also makes it
possible to catch some other potential footguns, for example, by transforming an
otherwise invisible failure into a Result:
type Error = String;
impl FfiWrapper {
pub fn new(val: u32) -> Result<Self, Error> {
let p: *mut FfiStruct = unsafe { new_struct(val) };
// Raw pointers are not guaranteed to be non-NULL.
if p.is_null() {
Err("Failed to get inner struct!".into())
} else {
Ok(Self { inner: p })
}
}
}
The wrapper structure can then offer safe methods that allow use of the C library’s
functionality:
impl FfiWrapper {
pub fn set_byte(& mut self, b: u8) {
// Safety: relies on invariant that ìnnerìs non-NULL.
let r: & mut FfiStruct = unsafe { & mut *self.inner };
r.byte = b;
Item 34: Control what crosses FFI boundaries | 257
}
}
Alternatively, if the underlying C data structure has an equivalent Rust mapping, and
if it’s safe to directly manipulate that data structure, then implementations of the
AsRef and AsMut
impl AsMut<FfiStruct> for FfiWrapper {
fn as_mut(& mut self) -> & mut FfiStruct {
// Safety: ìnnerìs non-NULL.
unsafe { & mut *self.inner }
}
}
let mut wrapper = FfiWrapper::new(42).expect("real code would check");
// Directly modify the contents of the C-allocated data structure.
wrapper.as_mut().byte = 12;
This example illustrates a useful principle for dealing with FFI: encapsulate access to
an unsafe FFI library inside safe Rust code. This allows the rest of the application to
and avoid writing unsafe code. It also concentrates all of the dangerous code in one place, which you can then study (and test) carefully to
uncover problems—and treat as the most likely suspect when something does go
wrong.
Invoking Rust from C
What counts as “foreign” depends on where you’re standing: if you’re writing an
application in C, then it may be a Rust library that’s accessed via a foreign function
interface.
The basics of exposing a Rust library to C code are similar to the opposite direction:
• Rust functions that are exposed to C need an extern "C" marker to ensure
they’re C-compatible.
• Rust symbols are name mangled by default (like C++), so function definitions
also need a #[no_mangle] attribute to ensure that they’re accessible via a simple
name. This in turn means that the function name is part of a single global name‐
space that can clash with any other symbol defined in the program. As such, con‐
sider using a prefix for exposed names to avoid ambiguities (mylib_…).
• Data structure definitions need the #[repr(C)] attribute to ensure that the layout
of the contents is compatible with an equivalent C data structure.
7 A Rust equivalent of the c++filt tool for translating mangled names back to programmer-visible names is
command.
258 | Chapter 6: Beyond Standard Rust
Also like the opposite direction, more subtle problems arise when dealing with point‐
ers, references, and lifetimes. A C pointer is different from a Rust reference, and you
forget that at your peril:
U N D E S I R E D B E H A V I O R
#[no_mangle]
pub extern "C" fn add_contents(p: *const FfiStruct) -> u32 {
// Convert the raw pointer provided by the caller into
// a Rust reference.
let s: & FfiStruct = unsafe { &*p }; // Ruh-roh
s.integer + s.byte as u32
}
/* C code invoking Rust. */
uint32_t result = add_contents(NULL); // Boom!
When you’re dealing with raw pointers, it’s your responsibility to ensure that any use
of them complies with Rust’s assumptions and guarantees around references:
#[no_mangle]
pub extern "C" fn add_contents_safer(p: *const FfiStruct) -> u32 {
let s = match unsafe { p.as_ref() } {
Some(r) => r,
None => return 0, // Pesky C code gave us a NULL.
};
s.integer + s.byte as u32
}
In these examples, the C code provides a raw pointer to the Rust code, and the Rust
code converts it to a reference in order to operate on the structure. But where did that
pointer come from? What does the Rust reference refer to?
The very first example in showed how Rust’s memory safety prevents refer‐
ences to expired stack objects from being returned; those problems reappear if you
hand out a raw pointer:
U N D E S I R E D B E H A V I O R
impl FfiStruct {
pub fn new(v: u32) -> Self {
Self {
byte: 0,
integer: v,
}
}
}
// No compilation errors here.
#[no_mangle]
Item 34: Control what crosses FFI boundaries | 259
pub extern "C" fn new_struct(v: u32) -> *mut FfiStruct {
let mut s = FfiStruct::new(v);
& mut s // return raw pointer to a stack object that's about to expire!
}
Any pointers passed back from Rust to C should generally refer to heap memory, not
stack memory. But naively trying to put the object on the heap via a Box doesn’t help:
U N D E S I R E D B E H A V I O R
// No compilation errors here either.
#[no_mangle]
pub extern "C" fn new_struct_heap(v: u32) -> *mut FfiStruct {
let s = FfiStruct::new(v); // creatèFfiStructòn stack
let mut b = Box::new(s); // movèFfiStruct` to heap
& mut *b // return raw pointer to a heap object that's about to expire!
}
The owning Box is on the stack, so when it goes out of scope, it will free the heap
object and the returned raw pointer will again be invalid.
, which abnegates responsibility for the heap object, effectively “forgetting” about it:
#[no_mangle]
pub extern "C" fn new_struct_raw(v: u32) -> *mut FfiStruct {
let s = FfiStruct::new(v); // creatèFfiStructòn stack
let b = Box::new(s); // movèFfiStruct` to heap
// Consume thèBoxànd take responsibility for the heap memory.
Box::into_raw(b)
}
This raises the question of how the heap object now gets freed. The previous advice
was to perform allocation and freeing of memory on the same side of the FFI bound‐
ary, which means that we need to persuade the Rust side of things to do the freeing.
The corresponding tool for the job is Box from a raw pointer:
#[no_mangle]
pub extern "C" fn free_struct_raw(p: *mut FfiStruct) {
if p.is_null() {
return; // Pesky C code gave us a NULL
}
let _b = unsafe {
// Safety: p is known to be non-NULL
Box::from_raw(p)
260 | Chapter 6: Beyond Standard Rust
};
} // `_b` drops at end of scope, freeing thèFfiStruct`
This still leaves the Rust code at the mercy of the C code; if the C code gets confused
and asks Rust to free the same pointer twice, Rust’s allocator is likely to become ter‐
minally confused.
That illustrates the general theme of this Item: using FFI exposes you to risks that
aren’t present in standard Rust. That may well be worthwhile, as long as you’re aware
of the dangers and costs involved. Controlling the details of what passes across the
FFI boundary helps to reduce that risk but by no means eliminates it.
Controlling the FFI boundary for C code invoking Rust also involves one final con‐
cern: if your R prevent panic!s
from crossing the FFI boundary, as this always results in undefined behavior—unde‐
fined but
Things to Remember
• Interfacing with code in other languages uses C as a least common denominator,
which means that symbols all live in a single global namespace.
• Minimize the chances of problems at the FFI boundary by doing the following:
— Encapsulating unsafe FFI code in safe wrappers
— Allocating and freeing memory consistently on one side of the boundary or
the other
— Making data structures use C-compatible layouts
— Using sized integer types
— Using FFI-related helpers from the standard library
— Preventing panic!s from escaping from Rust
Item 35: Prefer bindgen to manual FFI mappings
discussed the mechanics of invoking C code from a Rust program, describing
how declarations of C structures and functions need to have an equivalent Rust decla‐
ration to allow them to be used over FFI. The C and Rust declarations need to be kept
in sync, and t the toolchain wouldn’t help with this—mis‐
matches would be silently ignored, hiding problems that would arise later.
8 Note that Rust version 1.71 includes the , which makes some cross-language unwinding functionality possible.
Item 35: Prefer bindgen to manual FFI mappings | 261
Keeping two things perfectly in sync sounds like a good target for automation, and
the Rust project provides the right tool for the job: . The primary function of bindgen is to parse a C header file and emit the corresponding Rust declarations.
Taking some of the example C declarations from
/* File lib.h */
#include <stdint.h>
typedef struct {
uint8_t byte;
uint32_t integer;
} FfiStruct;
int add(int x, int y);
uint32_t add32(uint32_t x, uint32_t y);
the bindgen tool can be manually invoked (or invoked by a build.rs
create a corresponding Rust file:
% bindgen --no-layout-tests \
--allowlist-function="add.*" \
--allowlist-type=FfiStruct \
-o src/generated.rs \
lib.h
The generated Rust is identical to the handcrafted declarations in
/* automatically generated by rust-bindgen 0.59.2 */
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct FfiStruct {
pub byte: u8,
pub integer: u32,
}
extern "C" {
pub fn add(
x: ::std::os::raw::c_int,
y: ::std::os::raw::c_int,
) -> ::std::os::raw::c_int;
}
extern "C" {
pub fn add32(x: u32, y: u32) -> u32;
}
and can be pulled into R:
// Include the auto-generated Rust declarations.
include!("generated.rs");
262 | Chapter 6: Beyond Standard Rust
For anything but the most trivial FFI declarations, use bindgen to generate Rust bind‐
ings for C code—this is an area where machine-made, mass-produced code is defi‐
nitely preferable to artisanal handcrafted declarations. If a C function definition
changes, the C compiler will complain if the C declaration no longer matches the C
definition, but nothing will complain that a handcrafted Rust declaration no longer
matches the C declaration; auto-generating the Rust declaration from the C declara‐
tion ensures that the two stay in sync
This also means that the bindgen step is an ideal candidate to include in a CI system
ted code is included in source control, the CI system can error out if a freshly generated file doesn’t match the checked-in version.
The bindgen tool comes into its own when you’re dealing with an existing C codebase
that has a large API. Creating Rust equivalents to a big lib_api.h header file is manual
and tedious, and therefore error-prone—and as noted, many categories of mismatch
error will not be detected by the toolchain. bindgen of that allow specific subsets of an API to be targeted (such as the --allowlist-function
and --allowlist-type options previously illustrated).
This also allows a layered approach for exposing an existing C library in Rust; a com‐
mon convention for wrapping some xyzzy library is to have the following:
• An xyzzy-sys crate that holds (just) the bindgen-erated code—use of which is
necessarily unsafe
• An xyzzy crate that encapsulates the unsafe code and provides safe Rust access
to the underlying functionality
This concentrates the unsafe code in one layer and allows the rest of the program to
follow the advice in .
Beyond C
The bindgen tool has the ability to but only a subset and in a limited fashion. For better (but still somewhat limited) integration, consider using
the ate for C++/Rust interoperation. Instead of generating Rust code from C++
declarations, cxx takes the approach of auto-generating both Rust and C++ code from
a common schema, allowing for tighter integration.
9 The example also used the --no-layout-tests option to keep the output simple; by default, the generated
code will include #[test] code to check that structures are indeed laid out correctly.
Item 35: Prefer bindgen to manual FFI mappings | 263
Afterword
Hopefully the advice, suggestions, and information in this book will help you become
a fluent, productive Rust programmerdescribes, this book is intended
to cover the second step in this process, after you’ve learned the basics from a core
Rust reference book. But there are more steps you can take and directions to explore:
• Async Rust is not covered in this book but is likely to be needed for efficient, con‐
current server-side applications. The provides an introduction to async by Maxwell Flitton and Caroline Morton (O’Reilly, 2024) may also help.
• Moving in the other direction, bare-metal Rust might align with your interests
and requirements. This goes beyond the introduction to no_std to a
world where there’s no operating system and no allocation. The
troduction here.
• Regardless of whether your interests are low-level or high-level, the
ecosystem of third-party, open source crates is worth exploring—and contribu‐
ting to. Curated summaries like can help navigate the huge number of possibilities.
• Ror can provide help—and include a searchable index of questions that have been asked
(and answered!) previously.
• If you find yourself relying on an existing library that’s not written in Rust (as per
), you could rewrite it in Rust (RiiR). But don’t
to reproduce a battle-tested, mature codebase.
• As you become more skilled in Rust, Jon Gjengset’s Rust for Rustaceans (No
Starch, 2022) is an essential reference for more advanced aspects of Rust.
Good luck!
265
Index
Symbols
types available in,
! (exclamation mark),
allocation
2018 edition of Rust,
2021 edition of Rust,
allow attribute,
? (question mark) operator, , also-implemen
Android,
applied to Result itera
anon
converting result types to form where ?
any method (on Itera
a
Any trait,
anyhow cra
ar tool,
A
Arc type,
arra
arra
arra
abstract syn
access con
preferring from / into conversions over as
Adams, Douglas,
casts,
adapters,
AsM,
A
AsRef trait,
AddAssign trait,
assert_eq! macro,
AddressSanitizer,
associa
async,
aggrega
algebraic data type,
a
alignment,
attackers, code exposed to,
all method (on Itera
a
a
alloc library, ,
automatic type con
assumption that heap allocation can't fail,
Into trait automatically provided from From
implementa
267
B
Box::into_ra
Box::new method,
backward compa,
ownership of values on the heap,
crate a
broken_intra_doc_links crate attribute,
Barbarossa, H
BTreeMap type,
bare-metal R
BT
beha
build scripts
benchmarks,
dependency running arbitrary code during
bindgen,
BitAnd trait,
emitting C library linkage informa
build.rs,
BitOr trait,
builder pattern, ,
helper methods for populating fields,
BitXorAssign trait,
issues with
black_box function,
only one item built a
blanket trait implementations, ,
separating out stages of build process,
AsMut and BorrowM
support for building multiple items,
AsRef and Borrow traits,
byteorder cra
for smart poin
Bloch, Josh
C
making Boolean arguments clear with new‐
accessing C data from R
type,
C-like en
bootloaders,
default target for Rust's interoperability,
borrow checker, ,
for loop,
invoking C functions from R
checks moved from compile time to run‐
invoking R
deliberate error inserted for,
macro argument with side effects,
mutable references, moving value out and
manual allocations, malloc,
replacing it,
old-style pointers,
owner operations and,
pointers and references, comparison with
points to remember,
R
winning fights against,
preferring bindgen to FFI mappings,
creating self-referential data structures,
using da
printf statemen
using smart poin
== operator,
allocation failures, caught with exception
B
BorrowM
alloca
Bos, Mara, ,
bounds checking,
bounds method,
code that manually locks and unlocks a
Box type,
m
Box::from_raw method,
combination of enum with union,
268 | Index
version selection algorithm,
cxx crate for interoperating with R
data races in,
exception safety for templa
(see also Clippy)
exceptions and templa
,
explicit constraints on templa
cargo fm
for statement, loop declara
cargo metadata,
interoperating with R
iostream, operator<< overload,
cargo tree,
cargo upda
cargo-den
cargo-expand,
one definition rule for C/C++ code accessed
cargo-fuzz,
via R
cargo-semver-checks,
operator overloads, avoiding for unrelated
cargo-server-checks,
types,
cargo-tarpa
pointers and references, comparison with
Cargo.lock file,
R
Cargo.toml file,
RAII patterns in,
dependencies section,
RAII patterns used for memory manage‐
determining crate's features in,
ment,
links key,
run-time type identification (RTTI),
cargo:rustc-link-lib,
shared_pointer,
cargo:rustc-link-search,
switch arms for enums, warning about
missing,
preferring from / into conversions over as
templa
casts,
Rust code appearing to make implicit casts,
visibility guidelines,
catch_un
c++filt,
not implementing Sync,
C-un
cfg attribute,
cackle maniacally,
cfg(test) a
Cargill, Tom,
cfg_attr attribute,
char * (C string),
allowing multiple versions of a crate in a
automatic dependency selection by semver,
Clippy, ,
links to webpage describing error,
feature activa
warnings about R
restrictions in m
warnings corresponding to Items in this
searching for tools,
supporting different versions of library crate
Clone trait,
linked into single binary,
Copy trait implementa
tests included in published cra
Index | 269
traits that im
check spotting new potentially panicking
where it can't or shouldn't be implemented,
code,
integrating useful tools in
clone-on-write, ,
integra
cloned method,
cloning data in data structures,
testing example code,
closures,
con
in itera
avoiding nonlocal operations in macros,
failures of,
copied method,
with locks held, avoiding invoking,
copies, reducing,
traits represen
copy semantics, ,
code bloa
,
deciding whether to implemen
code, distinguishing from text,
core librar
collections
no heap allocation performed by,
allowing iteration over contents,
using full type names,
fallible alloca
Cow enum,
preceded by &,
CPAN (Comprehensive Perl Archive Network),
comments, documentation,
crates,
describing how some other code uses the
caution with use of another crate's types in
method,
your API,
focusing on the wh
crate names and feature names sharing
out-of-sync commen
namespace,
external, using instead of macros,
com
multiple versions in dependency graph,
com
no_std compatibility,
Comprehensive Rust online course,
proc-macro cra
concepts in writing R
pub(crate) visibility,
semantic versioning for a
concrete methods,
seman
(see also shared-state parallelism)
tool emitting informa
conditional compila
vulnerability to dependency problems,
cra,
config options, name/value varian
encapsulating unsafe code,
config-specific code,
names for crates published on,
published crate documentation,
const (poin
tests in published cra
third-party, open source crates,
constan
criterion crate,
cross-compilation,
consuming iterators,
cross-reference iden
continuous integration,
cross-references (in documentation),
bindgen step in,
CR
270 | Index
re-exporting dependencies whose types
cxx crate,
appear in your API, -
selection by cargo by semantic versioning,
D
da
testing functionality of,
wildcard-imported, pinning to precise ver‐
in Rust,
sion,
data structures,
dependency graph, ,
and alloca
expanded, controlling exposure to with
designing with borrow checker in mind,
optional features,
-
multiple versions of a cra
lifetimes in,
solving problems with tooling,
made available from alloc,
dependency upgrades,
Rust data structures exposed to C,
deprecated attribute,
self-referential,
Deref trait,
DerefMut trait, ,
using macros to a
derive a,
deadlocks,
, -
reducing chance of in shared-state parallel‐
declaring associated helper a
preferring to procedural macro that emits a
Debug trait,
DeriveInput data structure,
from derive macro,
derive_builder crate,
fmt method,
DebugDraw trait,
declara
development environmen
diagnostic informa
expression with side effects as in
discussion forums,
forma
Displa
hygienic macros,
inserting code at point of invoca
DivAssign trait,
preferring macros with behavior aligned
dividing by zero,
with normal R
default features,
default implementations,
Defa
documentation
combination with struct update syntax,
conventions for documentation comments,
defer statemen
documenting public interfaces,
denial-of-service (DoS) a
additional documentation locations,
Dependabot,
published crate documenta
Rust documentation comment format,
deciding when to take on,
tooling for,
dependencies section of Cargo.toml,
what not to document,
indirect,
requiring,
nonbreaking change causing compilation
for Rust standard librar
failure,
domain-specific language (DSL),
DoubleEndedItera
Index | 271
downcast_mut method,
borrowing rule broken,
Draw trait,
more complex problems with borrow
methods accepting Shape and,
checker,
vtable for,
Error trait,
errors,
Drop trait,
from borrow checker,
implementing for synchronization
mapping,
preferring idiomatic Error types,
implementing for types holding resources
minimal implementations,
that m
nested errors,
implementing RAII pattern,
preferring Result to Option for expressing
errors,
in multithreaded environment,
processing if function fails, using Result
dynamically typed languages,
dynamic_cast (C++),
propagation with use of explicit match
expression,
E
in type con
Effective C++ (M
ExactSizeItera
Effective Java (Bloch),
example code, ,
efficiency (optimal), fine-tuning for,
testing, ,
elision rules (see lifetime elision rules)
embedded platforms,
enumerate method,
,
en,
expect_err method,
a
explicit casts,
explicit constraints on target type,
extern C,
Defa
extern cra
derive macro use,
extern cra
deriving Copy,
disadvan
F
enum types,
f32 type,
f64 type,
Result,
failures
field comparisons with Ord and PartialOrd,
allocation failure,
designing software to cope with,
holding values not in list of allowed var‐
fallible allocation,
iants,
fat pointers,
eprintln! macro,
@FearlessSon,
Eq trait,
feature option,
feature unifica
features,
Err variant of Result,
no_standard and no_alloc, a
error conditions,
std and alloc,
error messages
272 | Index
FFI (foreign-function in, integral conversions where destination type
includes all possible values of source,
accessing C data from R
preferring from / into conversions over as
encapsulating access to unsafe library in safe
casts,
Rust code,
reflexive implementa
invoking C functions from R
use in user-defined type conversions,
FromIterator trait,
linking logistics,
function poin
for all trait implementa
invoking Rust from C,
function signa
lifetimes,
function-like macros,
linking with code from languages other than
functions,
R
currently executing function, sta
panic propagation interacting badly with
rela
preferring bindgen to FFI mappings,
R
-
using trait objects,
using existing library via, cost-benefit analy‐
using unsafe code,
fuzz tests,
file! MA
G
generics,
generic functions,
firm
generic method accepting items implement‐
flatten method,
ing Shape,
flattening stream of Option / Result values,
floating point types,
single-precision format,
trait bounds,
fm
trait bounds type parameter implementing
fn (function pointer) types,
m
Fn trait, ,
trait having generic function,
FnM,
,
fold method,
Gjengset, J
,
glob im
for,
(see also wildcard imports)
foreign function interface (see FFI)
Go, , ,
formal verifica
Godbolt compiler explorer, ,
forma
Goldilocks version specification (dependen‐
forma
for_each method,
Google OSS-Fuzz program,
Graham, Paul,
gratuitous use of La
from / into con
Group en
From implementation and Into trait
implementation for suberror types,
H
implementation for type conversions,
H
HashMa,
Index | 273
HashSet type,
blanket trait implementa
heap, ,
IntoIterator trait,
C code freeing heap memor
invarian,
freeing memory from Rust side,
internal, preserving in presence of excep‐
heap allocation possible, but ma
RAII types,
no heap allocation for core librar
no_std environment supporting heap allo‐
isize,
ca
is_empty method,
pointers passed from R
Item type,
value allocated on and never freed,
Ord implementa
Here B
h
iterables,
Hyrum's Law, ,
iterator adaptors (see iterator transforms)
Iterator trait,
I
manipula
methods to select single value from collec‐
i16 type,
i32 type,
single required method and default method
i64 type, ,
implementa
iterator transforms,
con
integrating tools into,
transformation expressions,
immutable references,
iterators,
owner operations and,
building collections from Result values,
impl Trait,
iterator consumers,
IndexM
iter_mut method,
infallible allocation,
disabling,
J
Ja,
inherent implementation,
inline functions,
L
int type (in C),
integer types
lattice,
converting larger to smaller, compile-time
let keyword,
error,
lib.rs,
matching pointer size, signed and unsigned,
libF
libraries
specific sized, signed and unsigned,
crates,
integration tests,
making library code no_std-compatible,
in
interior m
alloc library,
internationaliza
core library,
Into trait,
fallible allocation,
automatically provided with From trait
implementa
multiple versions of same librar
274 | Index
unsafe code in Rust standard libraries,
converting to iterator transforms,
licenses,
increasing convenience and abstraction of,
changing license for open source crate,
lifetime bound,
old-style, bounds checking and,
lifetime checks, ,
rendering a list of shapes, OO example,
using explicit loops instead of iterator trans‐
anonymous lifetimes and,
using iterator transforms instead of,
lifetime of a value, correlation with lifecycle of a
lossy con
resource,
LowerH
M
macros, , -
algebra of, -
declara-
anon
expression with side effects as in
in da
formatting values,
evolution of,
inserting code at point of invocation,
issue with in multithreaded environment,
preferring macros with behavior aligned
with normal Rust,
of items on the hea
disadvantages of,
lifetime reduction,
general advice on use of,
lifetimes of multiple arguments,
procedural,
references,
(see also procedural macros)
scope of,
sta
main function,
line! macro,
ma
link attribute,
ma
ma
links key,
M
lin
marker traits,
(see also Clippy)
Linux kernel,
standard, affecting copy seman
Liskov substitution,
standard, affecting use of Rust objects
local code refactoring,
match expressions,
localiza
explicit, preferring Option and Result trans‐
lock in
forms over,
max method,
locking hierarchy,
max_by method,
locks,
memory
(see also deadlocks)
allocating and freeing on same side of FFI
correct use of,
boundar
program memory layout,
reducing scope to prevent deadlocks,
protections in Rust,
in Rust,
safety guarantees without runtime overhead,
log cra
safety of, ,
safety problems in C/C++,
Index | 275
safety problems in Rust,
memor
mutex (in C++),
memory management, RAII patterns used for,
Mutex type, , ,
memory poin
da
memory synchroniza
providing interior mutability in multithrea‐
metadata,
ded environmen
meta
M,
functions in traits,
N
on public traits, avoiding feature-ga
visibility,
crates and features,
Meyers, Scott,
name mangling, ,
named_mut method,
NaN (not a n
minimum supported Rust version (MSR
Neg trait,
minor version,
newtype pa-
function opera
misma
limita
missing copy implementations compiler lin
making Boolean argument clear with,
missing Debug implementations compiler lint,
missing safety doc Clippy lint,
--no-default-features flag (build command),
pub conten
,
visibility,
None varian,
wildcard im
nonstandard Rust environmen
monomorphiza,
non_exhaustive a
different monomorphized function for each
Not trait,
type called with,
no_deadlocks cra
More Effective C++ (Meyers),
no_global_oom_handling config flag,
no_mangle attribute,
moving an item,
no_panic cra
da
no_std a,
items moved to or from the stack,
binary, difficulty of creating,
M
core library in no_std environments,
MulAssign trait,
opting in to alloc use,
multithreading,
writing code for no_std environment,
general advice on multithreaded code,
nth element of itera
m
accessing contents of Rust items with,
O
borrow checker rule for,
conversion to immutable,
object-oriented programming
owner operations and,
rendering a list of shapes,
replacing item content and returning previ‐
Ok variant of Result,
once_cell cra
276 | Index
open source,
operating systems
parameters (function), limiting,
resources,
synchroniza
parse_macro_input! macro,
systems not ha
PartialEq trait
target_os config value,
operator overloads,
required by PartialOrd,
optimization,
PartialOrd trait
avoiding over-optimization,
compiler optimizations giving misleading
pa
modifying, move restriction and,
performance,
preferring Option and Result transforms
over explicit ma-
Pointer trait,
Option::None varian
pointer traits,
pointers,
in C,
coercion of poin
bypassing for traits,
passed back from R
OSS-Fuzz program,
raw (see raw pointers)
owner links in tree-like da
in Rust and C,
owner of Rust items
smart pointer types,
accessing item conten
unnecessary poin
operations performed by,
poisoning (locks),
polymorphic type,
data structures owning all da
of items on the hea
preconditions,
shifting from single-owner to shared-owner
prelude,
principle of least astonishmen
printf (C),
P
println! macro,
package manager for Rust (see Cargo)
private,
panic! macro
preferring private code,
code path triggering, resulting in DoS
proc macros (see procedural macros)
a
proc-macro crate,
entrypoin
proc-macro2 cra
sensible uses of,
-
panics,
attribute macros,
alternative to for dealing with error condi‐
derive macros,
tions,
function-like macros,
a
types of,
Clippy checks for use of,
product method,
default behavior and alternatives to,
documenting,
Programming Rust (Blandy et al.),
on failure,
function that panics on invalid in
pub(cra
preventing from crossing FFI boundary,
pub(in crate),
Index | 277
pub(in pa
,
pub(self
RemAssign trait,
pub(super),
render method,
public fields in structures, avoiding feature-
gating,
repr(C),
push method,
repr(transparent),
Python,
Resource Acquisition Is Initialization (see RAII
pattern)
R
RAII (Resource Acquisition Is Initialization)
avoiding unwra
pattern,
building collections from values,
wrapper that prevents C memory leaks,
error types as second type argumen
rand crate,
holding MutexGuard,
random number genera
manipulations of, Clippy pointing out
R
raw identifier prefix (r#),
preferring to Option for expressing errors,
raw memor
raw pointers, ,
returned by release method,
, ,
returned in type conversions,
not implementing Send,
returning with proper error type instead of
re-export,
panic!,
readability,
values from try_borrow,
Result::Err varian
reduce method,
return statemen
rewrite it in Rust (RiiR),
RefCell type,
RTTI (run-time type identification),
not implementing Sync,
Rust Atomics and Locks (B
reference-counted poin
Rust for R
Rust Fuzz Book,
Rust pla
coercions of reference types,
The Rust Programming Language (Klabnik and
crea
Nichols),
lifetime scope and,
lifetimes of,
RustCrypto crates,
macro expansions that insert, avoiding,
rustfilt,
mutable,
rustfmt, inapplicability to macro invocations,
prevention of references to expired stack
objects,
Rustonomicon,
and smart pointers,
rustup,
stored in data structures, lifetimes of,
R
reflection, ,
languages with full reflection support,
S
reflection-like features in R
upcasting in future R
scan method,
reflexive implementation of From,
scope,
registry of crates,
reducing for locks,
release method,
278 | Index
reference scope smaller than lifetime of item
bounds checking,
slice types in structures, lifetime parameter
rules for declarative macro use,
segments (stack),
smart poin
beha
Self type,
blanket trait implementa
object safety of types with sizes known at
compile time,
user
trait object safety and,
Some variant of Option,
self-referential da
sorting, using marker trait in,
semantic versioning, -
spin crate,
for crate authors,
for crate users,
square_once! macro, ,
essentials of,
stack,
multiple semver-incompatible versions of a
introduction to,
crate in the build,
prevention of references to expired stack
ranges of acceptable versions,
objects,
understanding its concepts and limitations,
stack poin
sta
updating,
static library file,
version specification for dependencies,
static lifetime, ,
semi-automatic type con
sta
Send trait,
static, global variable marked as,
Sender/Receiver pair,
sta
separate compilation,
serde cra
std librar
sets,
code for environments where full std not
Sha
provided,
methods accepting, Draw and,
core library types equivalent to,
requirement for Draw implementa
inability to cope with failed alloca
vtable for,
no_std crate depending on std-using
shapes, rendering in OO example,
shared-sta
replacing std types with identical core and
advice for a
alloc crates,
data races, -
wide variety of common tasks covered in,
shared_poin
std::any::type_name,
std::any:tname,
ShlAssign trait,
std::fmt,
should_panic attribute,
std::mem functions,
Shr trait,
std::os::raw::c_int, size of,
, ,
Sized trait,
absence of a value,
skip method,
String::from_utf8,
skip_while method,
strings,
library function returning first line of file as
slices, ,
string,
Index | 279
in R
in C and R
unit tests,
contents made public, avoiding making
fields feature-dependen
@thingska
derive macro use,
thread safety in Rust,
encapsulating interaction with C library in
thread-compa
wrapper struct,
field-by-field comparison for Eq,
thread-safe,
methods added to,
threading,
private fields in,
threads
using builder pattern with complex structs,
deadlock sequence,
sharing state between,
struct update syntax,
TL
,
SubAssign trait,
TokenTree::Punct tokens,
subtyping,
Tolnay, David,
suggestions for future learning,
sum method, ,
supply chain a
swa
syn crate, ,
development environmen
Sync trait,
synchronization primitives, ,
ToOwned trait,
keeping two data structures in sync,
trait bounds, ,
T
trait methods im
T (target type) in generics, monomorphization
in
using Into trait for in type conversions,
take function,
trait coherence rules,
take method,
trait methods with default implementations,
take_while method,
target_arch config option,
trait objects,
different compile time and runtime types,
target_has_a
target_os config option,
encoding implementation vtable for only
target_pointer_width config option,
templates (C++),
test attribute,
no conversions between related trait objects,
, -
safety,
for traits ha
of exam
upcasting in Rust version 1.76,
fuzz tests,
integra
280 | Index
attempting to implement foreign trait for
auto-generated implementations from
derive macros,
type coercion, ,
bypassing orphan rule for,
implicit coercion,
coercion of concrete item into trait object,
type con-
common standard traits,
coercion,
error attempting to convert larger integer
example for displaying graphical objects,
type into smaller integer type,
Fn, FnOnce, and FnMut,
iterator,
man
user-defined,
marker,
type safety,
new upcasting feature,
of function signatures,
newtype and traits implemented on inner
type system,
type,
encoding behavior in
operator overload traits summary,
not documenting things already expressed
poin
public, with feature gate on a method,
type-length-value (TL
relating to operator overloads,
TypeI
standard traits,
implementation checks by Clippy,
used to display data in forma
aggregate,
in user-defined type con
closures,
using default implementations to minimize
core and std,
transforma
expressing common beha
(see also iterator transforms)
full type names required when not using
tree data structures,
function pointers,
TryFrom trait,
functions,
blanket implementation for any type imple‐
fundamental,
menting Into trait,
implementations for source not fitting desti‐
char,
na
floating point types,
implementations for standard library types,
integer types matching pointer size on
target system,
use in user-defined type con
integer types with specific sizes,
TryInto trait, ,
stricter conversions in R
try_.. methods,
try_borrow method,
idiomatic Error types, preferring,
try_find method,
including as much semantics as possible
tr
Rust type system,
tr
making common types available without
tr
using full names,
try_reserve method,
methods,
Index | 281
newtype pa-
use-after-free,
preferring Option and Result transforms
user-defined type con
over explicit ma-
user-defined types
sized types a
opera
usize,
(see also trait bounds; trait objects;
UTF-8,
traits)
V
V,
using builders for com-
Result holding a V
visibility,
Vec::push,
U
bounds checking,
u128 type,
converting vector of i64 values to bytes (u8),
verification (formal),
values not valid for Unicode code poin
version control, Cargo.lock file,
version selection algorithm (Cargo),
u8 type, ,
version specifica
Unicode,
visibility, -
union,
syn
common elemen
void (C),
optional elements,
vtable,
unit type,
entry for Any,
unreachable! macro,
unsafe code, ,
trait objects for trait bounds,
avoiding writing,
documenting,
W
FFI code in R
warn(missing_docs) a
encapsula
warnings about Rust usage (Clippy),
Weak type,
raw poin
wildcard dependency,
using raw and un-smart pointers,
wildcard im
unsafe functions,
avoiding from crates you don't con
Clippy checks on,
name clashes from,
unsafe_op_in_unsafe_fn lin
Wilde, Oscar,
unstable features in Rust,
Winters, T
unwrap method,
wrapper la
unwra
wrapper struct for C library in
upcasting in future Rust versions,
Z
use statements,
zip method,
core types in no_std environmen
282 | Index
About the Author
David Drysdale is a staff software engineer at Google and has been primarily work‐
ing in Rust since 2019. He is the author of the Rust port of the Tink cryptography
library and also led the project to replace Android’s hardware cryptography library
(KeyMint) with a Rust version. He has extensive prior experience in C/C++ and Go
and has previously worked on projects as diverse as the Linux kernel, networking
control plane software, and mobile video conferencing apps.
Colophon
The animal on the cover of Effective Rust is a speckled swimming crab ( Arenaeus cri‐
brarius). It is known for its nocturnal and solitary behavior, but can exhibit aggressive
qualities when threatened. The animal can be found in several places along the Atlan‐
tic shoreline from Massachusetts in the United States to areas in Argentina.
The speckled swimming crab’s hard upper shell can be light brown, light maroon, or
olive with white or tan spots. Each side of the carapace has nine lateral teeth, and
there are six other teeth found between the crab’s eye sockets. The animal can reach
between 4.5 and 6 inches wide, and it is twice as wide as it is long.
The speckled swimming crab’s colors allow it to camouflage in its surrounding areas
while waiting for its prey, which primarily consists of detritus. The animal has also
been known to eat fish, mollusks, and other crustaceans.
The conservation status of the speckled swimming crab has not been evaluated, but
the species is commercially gathered in Brazil, which may influence its population
size. Many of the animals on O’Reilly covers are endangered; all of them are impor‐
tant to the world.
The cover illustration is by Karen Montgomery, based on an antique line engraving
from Museum of Animated Nature. The series design is by Edie Freedman, Ellie
Volckhausen, and Karen Montgomery. The cover fonts are Gilroy Semibold and
Guardian Sans. The text font is Adobe Minion Pro; the heading font is Adobe Myriad
Condensed; and the code font is Dalton Maag’s Ubuntu Mono.
75.19
75 7x9. 1
eilly Media, Inc’Rf O
ademark o
ed trter
egis
eilly is a r’R. O
eilly Media, Inc’R
2023 O©