Chapter 11: Multiple threads and a lot more
This chapter introduction previews advanced Rust topics, including type aliases, Cow, Rc, and multiple threads, without explaining them in depth.
After this chapter you will be able to
- Imports inside functions. Explain that a `use` statement can go inside a function rather than only at the top of a program, letting code in that function use shorter names for imported enum variants.
- Renaming imported items. Explain how `as` can give an imported item a different name, and relate that to the section's example of shortening an awkward enum-variant name.
- Prototyping with todo placeholders. Explain how todo! lets you leave a function unfinished while compiling and trying the rest of a program.
- Runtime panic from todo. Describe what happens if a program calls code containing todo!.
- Type alias purpose. Explain that a type alias gives an existing type another name, either to describe it more clearly or to shorten a long type name. Describe importing items as another way to shorten a type name.
- Alias identity and orphan rule. Explain why `type File = String;` is still treated as `String` by the compiler and why type aliases do not violate the orphan rule.
- Borrowed and owned alternatives. Explain that Cow can represent either a borrowed value or an owned value, so code can use a &str when borrowing suffices and a String when ownership is needed.
- String variant selection. Trace why a string literal converted with .into() takes Cow’s Borrowed variant, while a String made by format!() takes its Owned variant.
- Rc and shared ownership. Explain that Rc means reference counter or reference counted and allows shared ownership while keeping track of how long data is shared, without breaking Rust’s ownership rules.
- Shared ownership with Rc. Explain why passing a String to a function can prevent its original owner from using it, and why Rc can be useful when cloning is undesirable or impossible. Describe how Rc tracks owners and when the value can be dropped.
- Sharing values with Rc. Explain how wrapping a value in Rc and cloning the Rc lets multiple owners use the same data without cloning the whole String. Describe how the two-function example changes its parameter types to Rc<String>.
- Shared city names with Rc. Explain how the example replaces borrowed city names with Rc<String> to remove the lifetime annotations, and distinguish cloning an Rc from cloning a String.
- Rust OS thread basics. Explain how threads let a program do multiple things at the same time and describe what gives Rust’s OS threads independent state.
- Spawning threads with closures. Describe how std::thread::spawn uses a closure to specify what a new thread does, and how repeated calls start independent threads.
- Main thread and unpredictable output. Explain why spawned threads may print different numbers of messages on different runs, or none at all.
- Waiting with JoinHandles. Explain that spawn() returns a JoinHandle and that calling .join() on it makes main() wait for that thread to finish.
- Joining after all spawns. Distinguish joining each thread immediately from storing all the JoinHandles in a Vec and joining them after every thread has been started.
- Closure capture traits. Distinguish Fn, FnMut, and FnOnce by how a closure uses a captured variable, and explain why the example that takes a value cannot be called twice.
- Ownership in spawned closures. Explain why a closure that prints an outside value tries to borrow it, why that causes a problem with `spawn()`, and how `move` makes the closure take ownership instead.
What it builds on
- Enums (chapter 4) · Enum variants provide the long names used in the example.
- The orphan rule (chapter 7) · The earlier introduction of the orphan rule.
- Lifetime annotations (chapter 10) · The meaning of lifetime annotations for references.
- Lifetime annotations in types (chapter 10) · Lifetime annotations in types.
- The stack, the heap, pointers, and references (chapter 2) · Earlier discussion of memory and ownership.
- Closures and closures inside iterators (chapter 8) · Introduces closures.
- Lifetimes in functions (chapter 10) · Introduces lifetime annotations in functions.
Section by section
Introduction
This chapter introduction previews advanced Rust topics, including type aliases, Cow, Rc, and multiple threads, without explaining them in depth.
11.1 Importing and renaming inside a function
This section shows how to place imports inside a function, use wildcard imports cautiously, and rename imported items with `as`.
- Imports inside functions: Explain that a `use` statement can go inside a function rather than only at the top of a program, letting code in that function use shorter names for imported enum variants.
Why it matters: A local import can make code in a function less repetitive. - Wildcard import tradeoff: Describe what `::*` imports and why the section advises against using it indiscriminately with other people's code.
Why it matters: Importing everything saves typing, but may bring in names that conflict with your own. - Renaming imported items: Explain how `as` can give an imported item a different name, and relate that to the section's example of shortening an awkward enum-variant name.
Why it matters: Renaming an import can resolve an inconvenient name or make code easier to write.
Key terms: use, MapDirection, North, NorthEast, ::*, prelude, as, OtherDirectory, FileState, SimilarFileNameInNextDirectory
11.2 The todo! macro
This section shows how todo! lets you compile a prototype with unfinished code, while explaining its limits and what happens if execution reaches it.
- Prototyping with todo placeholders: Explain how todo! lets you leave a function unfinished while compiling and trying the rest of a program.
Why it matters: You can sketch a program’s structure without finishing every function at once. - Runtime panic from todo: Describe what happens if a program calls code containing todo!.
Why it matters: A compiling prototype is not safe to run through its unfinished parts. - Limits of todo placeholders: Explain why todo! does not make an invalid function signature compile, and state one other place the section says it can be used.
Why it matters: A placeholder helps with unfinished code, not with types Rust cannot identify.
Key terms: todo!, prototyping, panic, signatures, undeclared types, struct parameters
11.3 Type aliases
This section explains how type aliases give existing types clearer or shorter names without creating new types.
- Type alias purpose: Explain that a type alias gives an existing type another name, either to describe it more clearly or to shorten a long type name. Describe importing items as another way to shorten a type name.
Why it matters: A clearer or shorter name can make code easier to read without changing its type. - Alias identity and orphan rule: Explain why `type File = String;` is still treated as `String` by the compiler and why type aliases do not violate the orphan rule.
Why it matters: Remembering that an alias is not a new type helps you reason about what the compiler sees.
Key terms: type alias, import items, orphan rule, String
11.4 Cow
Introduces Cow as a way to hold either a borrowed value or an owned value, and shows how that choice applies to strings, functions, and structs.
- Borrowed and owned alternatives: Explain that Cow can represent either a borrowed value or an owned value, so code can use a &str when borrowing suffices and a String when ownership is needed.
Why it matters: Cow lets one type accommodate both borrowing and ownership when either may be appropriate. - String variant selection: Trace why a string literal converted with .into() takes Cow’s Borrowed variant, while a String made by format!() takes its Owned variant.
Why it matters: The input’s borrowed or owned form explains which Cow variant the examples produce. - Generic bounds and lifetimes: Describe what the lifetime, ToOwned bound, and ?Sized bound contribute to the Cow signature, including why str can be used.
Why it matters: These parts of the signature explain how Cow can support borrowed str alongside an owned form. - Cow in functions and structs: Describe how the examples use Cow for usually borrowed messages with an owned error message, and for a User name that accepts either &str or String; explain when User needs a nonstatic lifetime.
Why it matters: The examples show where Cow’s borrowed-or-owned choice is useful in program interfaces.
Key terms: Cow, clone on write, Borrowed, Owned, Cow::Borrowed, format!(), .into(), 'a, ToOwned, ?Sized
11.5 Rc
Introduces Rc as a way to share ownership while respecting Rust’s ownership rules.
- Rc and shared ownership: Explain that Rc means reference counter or reference counted and allows shared ownership while keeping track of how long data is shared, without breaking Rust’s ownership rules.
Why it matters: This gives you the basic reason Rc is useful before you see it in practice.
Key terms: Rc, reference counter, reference counted, shared ownership
11.5.1 Why Rc exists
This section explains why Rc is useful when sharing ownership is preferable to cloning, and compares its ownership tracking with garbage collection.
- Shared ownership with Rc: Explain why passing a String to a function can prevent its original owner from using it, and why Rc can be useful when cloning is undesirable or impossible. Describe how Rc tracks owners and when the value can be dropped.
Why it matters: This explains why Rc is useful when making a separate copy of a value is not a good option. - Rc and garbage collection: Distinguish Rust’s use of Rc from garbage collection while describing the similarity the section draws between them.
Why it matters: The comparison helps explain why Rc can feel familiar to programmers coming from garbage-collected languages.
Key terms: Rc, owner, clone, dropped, garbage collection, references, lifetimes
11.5.2 Using Rc in practice
The section shows how to share data with Rc, count its owners, and use weak references to avoid reference cycles.
- Sharing values with Rc: Explain how wrapping a value in Rc and cloning the Rc lets multiple owners use the same data without cloning the whole String. Describe how the two-function example changes its parameter types to Rc<String>.
Why it matters: This lets the city structs or multiple functions share a String without making a separate copy for each owner. - Counting strong owners: Trace why Calgary’s city history has a strong count of three and state what strong_count() reports.
Why it matters: The count makes shared ownership visible in the city example. - Weak references and cycles: Explain why two Rcs that point to each other can prevent their values from dropping, and distinguish weak references from strong ones in that situation.
Why it matters: Weak references let linked values avoid keeping each other alive forever.
Key terms: Rc, Rc::clone(&item), Rc<String>, strong_count(), strong pointers, City, CityData, weak references, reference cycle, Rc::downgrade(&item)
11.5.3 Avoiding lifetime annotations with Rc
This section shows how sharing city names with Rc<String> avoids repeating lifetime annotations through nested structs without cloning each String.
- Lifetimes in nested structs: Trace why storing a borrowed city name requires lifetime annotations on City, Country, and World in the example.
Why it matters: It explains why the borrowed-name version becomes awkward as the structs are nested. - Shared city names with Rc: Explain how the example replaces borrowed city names with Rc<String> to remove the lifetime annotations, and distinguish cloning an Rc from cloning a String.
Why it matters: Rc offers a way to reuse the city names without repeatedly annotating lifetimes or cloning their Strings.
Key terms: lifetime, City, Country, World, Vec<City>, Vec<Country>, Rc<String>, Vec<Rc<String>>, clone the Rc, lifetime annotations
11.6 Multiple threads
This section introduces how Rust uses OS threads to do multiple things at the same time.
- Rust OS thread basics: Explain how threads let a program do multiple things at the same time and describe what gives Rust’s OS threads independent state.
Why it matters: This gives you the basic picture of what Rust’s threads are before you start using them.
Key terms: multiple threads, OS threads, state stack, local state
11.6.1 Spawning threads
This section shows how to spawn threads with closures and explains why their output may be unpredictable when the main thread finishes first.
- Spawning threads with closures: Describe how std::thread::spawn uses a closure to specify what a new thread does, and how repeated calls start independent threads.
Why it matters: This is the basic operation behind the section’s thread examples. - Main thread and unpredictable output: Explain why spawned threads may print different numbers of messages on different runs, or none at all.
Why it matters: Starting a thread does not guarantee its work will finish before the program exits. - JoinHandle and thread lifetime: State that spawn returns a JoinHandle that can be used to wait for threads instead of keeping main busy, and explain the section’s reason for the 'static requirement on the closure and its return value.
Why it matters: These constraints explain both how the section proposes waiting for threads and why spawned work must be able to outlive its creating scope.
Key terms: std::thread::spawn, closure, thread, main thread, main(), panic, JoinHandle, 'static lifetime, return value
11.6.2 Using JoinHandles to wait for threads to finish
The section shows how to use JoinHandles to keep main() running until spawned threads finish, and why joining after spawning every thread differs from joining each one immediately.
- Waiting with JoinHandles: Explain that spawn() returns a JoinHandle and that calling .join() on it makes main() wait for that thread to finish.
Why it matters: Joining prevents main() from finishing before a thread has completed its work. - Joining after all spawns: Distinguish joining each thread immediately from storing all the JoinHandles in a Vec and joining them after every thread has been started.
Why it matters: The order of spawning and joining determines whether threads get to start working before main() begins waiting.
Key terms: JoinHandle, spawn(), .join(), main(), Vec, JoinHandles
11.6.3 Types of closures
Introduces the three closure traits through how a closure captures variables and explains why taking a value can limit repeated calls.
- Closure capture traits: Distinguish Fn, FnMut, and FnOnce by how a closure uses a captured variable, and explain why the example that takes a value cannot be called twice.
Why it matters: These distinctions explain why changing or taking a captured value affects how a closure can be used.
Key terms: closure, Fn, FnMut, FnOnce, takes by value, mutable reference
11.6.4 Using the move keyword
Explains why a spawned thread needs a closure to own values it uses from outside, and how `move` makes that happen.
- Ownership in spawned closures: Explain why a closure that prints an outside value tries to borrow it, why that causes a problem with `spawn()`, and how `move` makes the closure take ownership instead.
Why it matters: This explains both the compiler complaint and why adding `move` lets the thread use the outside value.
Key terms: move, spawn(), Fn, FnOnce, borrow, take by value
11.7 Summary
Where this is used later
- Module basics (chapter 14) · The later discussion of modules can build on this introduction to importing names from other code.
- unreachable! (chapter 21) · The comparison with todo!.
- (chapter intro) (chapter 12) · The earlier discussion of Rc.
- Arc (chapter 12) · Rc gives a value more than one owner.
- Scoped threads (chapter 12) · Starting regular threads with thread::spawn()
- Using static mut in unsafe Rust (chapter 16) · Spawning threads
- The relationship between FnOnce, FnMut, and Fn (chapter 12) · The section develops the relationship among FnOnce, FnMut, and Fn.
- Closures as arguments (chapter 12) · The three closure traits Fn, FnMut, and FnOnce.
Concepts explained in this chapter
- RcRc is Rust's reference-counting smart pointer type for shared ownership of data while tracking the number of active owning references.
- JoinHandleJoinHandle is a Rust standard-library handle representing a spawned thread and allowing the program to wait for that thread to finish.
- spawnspawn is a Rust standard-library function that starts a new thread to execute a supplied closure and returns a handle for that thread.
- CowCow is a Rust standard-library clone-on-write enum that can represent either borrowed data or owned data and create an owned copy when necessary.
- moveMove semantics is the behavior in which transferring a non-Copy value transfers its ownership and prevents use of the original binding.
- threadA thread is an independently scheduled execution path within a program that can run concurrently with other threads.
- FnFn is a Rust trait for closures and callable values that can be called repeatedly through shared access without consuming or mutating their captured environment.
- FnOnceFnOnce is a Rust callable trait for closures and function-like values that may consume their captured environment and therefore can be called at most once.
- todo!todo! is a Rust standard-library macro that marks unfinished code and panics if the marked code is executed.
- FnMutFnMut is a Rust callable trait for closures and function-like values that can be invoked through mutable access and may mutate their captured environment.
- closureA closure is an anonymous Rust function-like value that can capture variables from its surrounding environment and be passed as an argument.
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.
