Chapter 16: Const, “unsafe” Rust, and external crates
The chapter introduces const generics and const functions, then previews unsafe Rust and external crates.
After this chapter you will be able to
- Const Generics and Arrays. Explain that const generics let code be generic over const values and are especially useful with arrays.
- Compile-Time Const Functions. Describe how const functions resemble regular functions but can be called at compile time, before the program starts.
- Parameterized buffer length. Describe how the Buffers example uses a type parameter T and a const parameter N to support different element types and array lengths in one struct.
- Const function call contexts. Explain that a const fn is permitted to run in a const context, where the compiler interprets it at compile time, but can also be called outside a const context.
- Const function eligibility. Explain why a regular fn cannot replace give_eight() when setting a const, and why not every function can be const. Describe how to check a candidate const fn and why the permitted operations can change across Rust versions.
- Const-initialized mutable global state. Explain why a mutable global is difficult in Rust and how a static Mutex containing an initially empty Vec can provide global state that is modified at run time.
- Mutable statics and unsafe. Describe how a static is declared mutable, how broadly its value can change, and that using a static mut requires the unsafe keyword.
- Reasons for unsafe Rust. Explain why Rust needs unsafe code for some systems programming and calls into other languages.
- Meaning of unsafe blocks. Distinguish the unsafe keyword from a claim that the code is necessarily wrong or dangerous, and explain the developer's responsibility.
- Mutable static access. Explain what `static mut` allows and why the compiler requires an `unsafe` block even to print its value.
- Concurrent increment results. Trace why the example with many threads incrementing `NUMBER` can finish below the expected total.
- Same-size bit reinterpretation. Explain that `transmute()` takes a value of one type and treats its bits as another type of the same size. Describe why transmuting `-19i32` to `u32` produces a different displayed number even though the bits remain the same.
- Validity beyond equal size. Explain why matching sizes do not make a `transmute()` safe. Use the eight-`i32` array and `User` example to describe the programmer’s responsibility to ensure the value is valid as both types.
- Unchecked unwrap assumptions. Explain that unsafe `.unwrap_unchecked()` assumes an `Option` contains `Some` or a `Result` contains `Ok`, and that violating the assumption causes undefined behavior.
- External crate meaning and sharing. Explain that an external crate is a crate other than the one you are working on, usually written by someone else, and that authors can publish crates on crates.io for others to use.
- Reasons for using external crates. Explain why bringing in external crates is normal in Rust: importing them is easy, the standard library is small, and developers can choose among crates offering similar functionality.
- Adding rand through Cargo. Describe how to add rand to a Cargo project and what happens when the project is run afterward.
- Random function relationship. Describe how rand's random() and thread_rng() functions relate in this section's example.
- One-to-ten range bounds. Explain which .gen_range() bounds the example uses to produce numbers from 1 to 10.
- Character stat rolling rules. Explain how three d6 rolls produce a stat and how rolling four d6 and discarding the lowest roll changes the result.
What it builds on
- Generics (chapter 5) · Introduces generics.
- Arrays (chapter 3) · Introduces arrays.
- const and static (chapter 2) · Introduces const in Rust.
- Lifetime annotations (chapter 10) · Lifetime parameters
- Mutex (chapter 10) · Using a Mutex to protect mutable data.
- The stack, the heap, pointers, and references (chapter 2) · Introduces memory addresses and pointers.
- Spawning threads (chapter 11) · Spawning threads
- Option and Result (chapter 5) · The Some and Ok variants of Option and Result.
Section by section
Introduction
The chapter introduces const generics and const functions, then previews unsafe Rust and external crates.
- Const Generics and Arrays: Explain that const generics let code be generic over const values and are especially useful with arrays.
Why it matters: This explains why const generics are relevant when working with arrays. - Compile-Time Const Functions: Describe how const functions resemble regular functions but can be called at compile time, before the program starts.
Why it matters: The timing of the call is the distinction this introduction makes about const functions.
Key terms: const generics, const values, arrays, const functions, compile time
16.1 Const generics
This section explains how const generics let one definition work with arrays of different lengths, avoiding the need for separate structs for each size.
- Three generic parameter kinds: Distinguish type, lifetime, and const generic parameters, and explain that const generics make an item generic over a const value.
Why it matters: It clarifies what varies when a definition uses a const generic. - Array length type restriction: Explain why arrays of the same element type but different lengths created a problem for reusable structs and trait implementations.
Why it matters: This is the array-related difficulty that motivates const generics. - Parameterized buffer length: Describe how the Buffers example uses a type parameter T and a const parameter N to support different element types and array lengths in one struct.
Why it matters: It shows how one definition can replace many fixed-size buffer definitions.
Key terms: type generics, lifetimes, const generics, arrays, array size, trait, Buffers, T, const N, usize
16.2 Const functions
This section explains that a const function can be called in a compile-time const context or used like a regular function, while the operations permitted in const functions depend on Rust's rules and version.
- Const function call contexts: Explain that a const fn is permitted to run in a const context, where the compiler interprets it at compile time, but can also be called outside a const context.
Why it matters: It prevents you from mistaking const fn for a function that only runs during compilation. - Const function eligibility: Explain why a regular fn cannot replace give_eight() when setting a const, and why not every function can be const. Describe how to check a candidate const fn and why the permitted operations can change across Rust versions.
Why it matters: It explains both the compiler error in the example and why whether a function can be const may depend on the Rust version.
Key terms: const fn, const context, compile time, allocations
16.3 Mutable statics
This section explains how const-initialized synchronization types let Rust programs create a global collection that can be modified at run time.
- Const-initialized mutable global state: Explain why a mutable global is difficult in Rust and how a static Mutex containing an initially empty Vec can provide global state that is modified at run time.
Why it matters: It shows how to start with an empty global collection while respecting Rust’s initialization rules.
Key terms: const context, const fn, static, Mutex, RwLock, Vec
16.4 Unsafe Rust
Introduces mutable statics, their program-wide changeability, and the requirement to use unsafe to access them.
- Mutable statics and unsafe: Describe how a static is declared mutable, how broadly its value can change, and that using a static mut requires the unsafe keyword.
Why it matters: This distinction prepares you to recognize why mutable statics need special care.
Key terms: static mut, static, const, unsafe keyword
16.4.1 Overview of unsafe Rust
This section explains why Rust has unsafe code, what responsibility the keyword signals, and how an unsafe function is called.
- Reasons for unsafe Rust: Explain why Rust needs unsafe code for some systems programming and calls into other languages.
Why it matters: It explains why a language focused on safety still provides an unsafe escape hatch. - Meaning of unsafe blocks: Distinguish the unsafe keyword from a claim that the code is necessarily wrong or dangerous, and explain the developer's responsibility.
Why it matters: The keyword is a warning to take responsibility, not a verdict that the code is broken. - Calling unsafe functions: Describe the relationship the section gives between an unsafe function and an unsafe block at its call site.
Why it matters: It identifies the call-site requirement shown in the section's example.
Key terms: unsafe, memory address, unsafe keyword, unsafe block, unsafe fn
16.4.2 Using static mut in unsafe Rust
This section shows how `static mut` permits direct changes to a static value, why accessing it requires `unsafe`, and how concurrent changes can produce unexpected results.
- Mutable static access: Explain what `static mut` allows and why the compiler requires an `unsafe` block even to print its value.
Why it matters: The `unsafe` requirement signals that direct access does not provide the protections a wrapper would. - Concurrent increment results: Trace why the example with many threads incrementing `NUMBER` can finish below the expected total.
Why it matters: The example makes the danger of unprotected concurrent access visible as missing increments. - Const and static distinction: Distinguish a `const` from a `static` using the difference stated at the end of the section.
Why it matters: This distinction explains how a mutable static is possible even though a const cannot change.
Key terms: static mut, unsafe, Mutex, undefined behavior, threads, NUMBER, data races, const, static
16.4.3 Rust’s most famous unsafe method
This section shows how `transmute()` reinterprets a value’s bits as another type and why equal size alone does not make that safe.
- Same-size bit reinterpretation: Explain that `transmute()` takes a value of one type and treats its bits as another type of the same size. Describe why transmuting `-19i32` to `u32` produces a different displayed number even though the bits remain the same.
Why it matters: The example separates a value’s bit pattern from the number a type makes those bits represent. - Validity beyond equal size: Explain why matching sizes do not make a `transmute()` safe. Use the eight-`i32` array and `User` example to describe the programmer’s responsibility to ensure the value is valid as both types.
Why it matters: Equal size is only enough for this code to compile, not a guarantee that the result is safe to use.
Key terms: transmute(), i32, u32, User, undefined behavior
16.4.4 Methods ending in _unchecked
This section explains the risks of unchecked unsafe methods, questions their performance benefit, and puts unsafe Rust in perspective as an optional tool for specialized work.
- Unchecked unwrap assumptions: Explain that unsafe `.unwrap_unchecked()` assumes an `Option` contains `Some` or a `Result` contains `Ok`, and that violating the assumption causes undefined behavior.
Why it matters: Skipping a check is only safe when you can uphold the assumption it would have checked. - Unchecked performance tradeoff: Explain why an unchecked method is not guaranteed to run faster than its safe counterpart, despite being tried for performance.
Why it matters: Avoiding a check does not by itself establish a performance benefit worth taking an unsafe risk. - Unsafe Rust practical role: Describe why most Rust programmers need not use unsafe, while low-level systems work or direct links to other languages may call for its flexibility.
Why it matters: Unsafe is a specialized option, not a requirement for becoming productive in Rust.
Key terms: _unchecked, unwrap_unchecked(), Option, Result, Some, Ok, unsafe, low-level systems programmer, C libraries
16.5 Introducing external crates
Introduces external crates, explains why Rust programmers commonly use them, and notes how the Playground makes popular crates available.
- External crate meaning and sharing: Explain that an external crate is a crate other than the one you are working on, usually written by someone else, and that authors can publish crates on crates.io for others to use.
Why it matters: This gives you the basic meaning of the crates you will use throughout the book. - Reasons for using external crates: Explain why bringing in external crates is normal in Rust: importing them is easy, the standard library is small, and developers can choose among crates offering similar functionality.
Why it matters: It explains why relying on other people's crates is an ordinary part of Rust programming. - Playground crate availability: Describe why readers can try popular external crates in the Rust Playground without installing Rust locally.
Why it matters: You can try the upcoming crate examples even if Rust is not installed on your computer.
Key terms: external crate, crates.io, external crates, Rust standard library, Playground
16.5.1 Crates and Cargo.toml
This section explains why rand is an external crate and how to add it to a Cargo project, run the project, and find the crate’s documentation.
- Rand as an external crate: Explain why the book uses the rand crate for random numbers rather than a standard-library function, and what it means here to call rand a “blessed crate.”
Why it matters: It explains why using rand requires adding a dependency. - Adding rand through Cargo: Describe how to add rand to a Cargo project and what happens when the project is run afterward.
Why it matters: This is the step that makes an external crate available to the project. - Using and finding crate documentation: Describe how the section says to recognize rand in a code path and find its documentation.
Why it matters: It gives the reader a way to locate rand’s API and recognize calls to it.
Key terms: standard library, rand, blessed crates, Cargo.toml, [dependencies], cargo add rand, cargo run, std, crates.io, docs
16.5.2 Using the rand crate
Shows how to use the rand crate in the Playground to generate random numbers, including numbers from 1 to 10.
- Playground crate availability: Explain why the reader can use rand in the Playground without revisiting Cargo.toml.
Why it matters: It explains why the example can go straight to using rand. - Random function relationship: Describe how rand's random() and thread_rng() functions relate in this section's example.
Why it matters: It connects the convenient random() call to the generator used behind it. - One-to-ten range bounds: Explain which .gen_range() bounds the example uses to produce numbers from 1 to 10.
Why it matters: The example shows how to request random numbers in a particular range.
Key terms: Playground, crates, Cargo.toml, rand, random(), thread_rng(), rng, gen(), .gen_range()
16.5.3 Rolling some dice with rand
Uses a dice-based character creator to show how different random-roll rules affect game stats.
- Character stat rolling rules: Explain how three d6 rolls produce a stat and how rolling four d6 and discarding the lowest roll changes the result.
Why it matters: The rolling rule changes how likely a character is to have a very low stat. - Character creator roll selection: Describe how the proposed character creator represents stats and lets the user choose a rolling method.
Why it matters: It connects the two dice rules to the program's stated design.
Key terms: d6, stat, Character struct, enum
16.6 Summary
Where this is used later
- Blanket trait implementations (chapter 17) · Const generics
- lazy_static and once_cell (chapter 17) · The idea of const functions.
- Lazy static: Lazily evaluated statics (chapter 17) · The distinction between const and non-const functions.
- Further development and cleanup (chapter 24) · The earlier introduction to const functions
- (chapter intro) (chapter 17) · Introduces external crates and how they fit into a Rust project.
- serde (chapter 17) · Using external crates.
Concepts explained in this chapter
- unsafe RustUnsafe Rust is the part of Rust that permits operations whose memory-safety conditions cannot be fully verified by the compiler and therefore require programmer responsibility.
- const genericsConst generics are Rust generics that parameterize types or implementations with compile-time constant values.
- crateAn external crate is a Rust compilation unit published outside the current project for other programs to depend on.
- randrand is a Rust crate that provides random-number generation facilities and related randomization utilities.
- unsafe blockAn unsafe block is a Rust code block that permits operations requiring the programmer to uphold safety invariants that the compiler cannot verify.
- const fnA const fn is a Rust function eligible for evaluation at compile time when called in a compile-time context.
- static mutA static mut is a Rust static value that can be directly modified, but accessing it requires unsafe code because unsynchronized access can violate aliasing rules or cause data races.
- undefined behaviorUndefined behavior is program execution whose effects are not constrained by the language specification because a required safety invariant has been violated.
- arrayAn array is a fixed-size Rust collection whose length is known at compile time and whose elements have one type.
- cargo.tomlCargo.toml is the manifest file that declares a Rust package's metadata and dependencies, including external crates and their versions.
- const qualifierA language feature that marks a value, binding, or access path as constant, restricting reassignment or modification according to the language’s rules.
- static fieldA Rust program item representing a value stored at a fixed memory location for the entire program lifetime.
- CargoCargo is Rust's package manager and build tool for creating, configuring, compiling, running, and managing Rust projects and their dependencies.
- pointer-sized integerA pointer-sized integer is a Rust signed or unsigned integer whose width matches the computer architecture.
- cargo addcargo add is a Cargo command that adds an external crate dependency to a package manifest.
- cargo runcargo run is a Cargo subcommand that builds and runs a Rust binary target while resolving its dependencies.
These guides are generated from the text of Manning books by Manning's knowledge graph and link to the sections they draw on. The full books are available on liveBook and at manning.com.
