Chapter 4: Lifetimes, ownership, and borrowing
The introduction explains why Rust’s borrow checker matters and gives basic meanings for ownership, lifetimes, and borrowing.
After this chapter you will be able to
- Borrow checker and safety. Explain that the borrow checker checks whether access to data is legal, helping Rust prevent safety issues.
- Ownership and value cleanup. Describe ownership here as responsibility for cleaning up values when they are no longer needed, rather than as property rights or exclusive ability to access them.
- Lifetimes and borrowed access. Distinguish a value’s lifetime—the period when accessing it is valid—from borrowing, which means accessing a value owned elsewhere without an obligation to return it.
- Simplified CubeSat constellation model. Describe how the example represents three CubeSats and why it does not yet model a ground station.
- Ownership moves into functions. Trace what happens to ownership when main() passes a CubeSat to check_status(), and explain why changing the satellite representation from an integer to a CubeSat struct exposes a compile-time issue.
- Lifetime versus variable scope. Explain why main() cannot use sat_a after passing it to check_status(), even though sat_a remains in main()’s local scope.
- Copy and move semantics. Distinguish what happens when a value implements Copy from what happens when a non-Copy value is passed to a function that takes ownership, and explain why the difference can surprise beginners.
- Owner cleanup responsibility. Explain that an owner is responsible for cleanup when its values’ lifetimes end, and that the compiler ordinarily arranges the destructor calls.
- Owner lifetime constraint. Explain why a value cannot outlive its owner and describe the consequence the section gives for reference-based structures such as trees.
- Ownership versus access control. Distinguish owning a value from having privileged access to it or authority over whether other code borrows it.
- Ownership transfer paths. Trace how ownership moves when a CubeSat value is assigned to another variable, passed to a function, or returned to its caller.
- Satellite ownership after sending. Explain why moving sat_a into base.send() is a problem when main still needs to use the satellite.
- Borrowing Instead of Ownership. Explain how a function can request only the access it needs: `&T` for read-only access or `&mut T` for read-write access, rather than taking ownership.
- Short-lived satellite objects. Explain why the CubeSat design creates satellite objects when needed instead of keeping them alive throughout the program.
- Separate message storage. Explain why messages must be stored outside the short-lived CubeSat instances and how the instances find messages intended for them.
- Copy and Clone behavior. Distinguish how Copy and Clone duplicate values, and explain what happens when an assignment or function call would otherwise move a Copy value.
- Reference-counted shared ownership. Explain how Rc<T> lets multiple places share ownership of a value and how its reference count determines when the value is dropped.
- Shared interior mutability. Describe why Rc<RefCell<T>> is used instead of Rc<T> when shared owners need to modify a value, including what interior mutability means in this example.
What it builds on
- Using references (chapter 2) · Earlier use of references provides context for accessing values without owning them.
- Modeling files with struct (chapter 3) · Uses a struct to model a value.
- Modifying a known global variable (chapter 3) · Introduces interior mutability.
Section by section
Introduction
The introduction explains why Rust’s borrow checker matters and gives basic meanings for ownership, lifetimes, and borrowing.
- Borrow checker and safety: Explain that the borrow checker checks whether access to data is legal, helping Rust prevent safety issues.
Why it matters: This explains why borrow-checker errors are connected to program safety. - Ownership and value cleanup: Describe ownership here as responsibility for cleaning up values when they are no longer needed, rather than as property rights or exclusive ability to access them.
Why it matters: This keeps the ownership metaphor from suggesting that only an owner can ever access a value. - Lifetimes and borrowed access: Distinguish a value’s lifetime—the period when accessing it is valid—from borrowing, which means accessing a value owned elsewhere without an obligation to return it.
Why it matters: These meanings provide a starting point for discussing when and how code may access a value.
Key terms: borrow checker, ownership, owner, lifetime, borrowing
4.1 Implementing a mock CubeSat ground station
This section introduces a simplified CubeSat program that will serve as the chapter’s working example.
- Simplified CubeSat constellation model: Describe how the example represents three CubeSats and why it does not yet model a ground station.
Why it matters: This simple model establishes the program used to explore ownership and lifetimes. - Stubbed satellite status checks: Explain how the example checks satellite status without implementing a realistic status system.
Why it matters: The status check keeps the example runnable without making realistic satellite behavior the focus.
Key terms: CubeSat, ground station, constellation, check_status(), enum, stub function
4.1.1 Encountering our first lifetime issue
A CubeSat example shows that passing a value to a function can move its ownership, ending its lifetime even while its variable remains in scope.
- Ownership moves into functions: Trace what happens to ownership when main() passes a CubeSat to check_status(), and explain why changing the satellite representation from an integer to a CubeSat struct exposes a compile-time issue.
Why it matters: This explains what the compiler means by a “moved” value in the CubeSat example. - Lifetime versus variable scope: Explain why main() cannot use sat_a after passing it to check_status(), even though sat_a remains in main()’s local scope.
Why it matters: A variable still being in scope does not mean its former value remains usable. - Rebinding without live borrows: Explain why the example may reassign a_status, and describe the condition under which overwriting a value would instead be rejected.
Why it matters: It distinguishes a legal reassignment in this example from an overwrite that conflicts with a live borrow.
Key terms: move, ownership, CubeSat, check_status(), lifetime, scope, drops, borrow checker, rebinding, live borrows
4.1.2 Special behavior of primitive types
This subsection explains why primitive values remain usable after being passed to a function while values of types without Copy may not.
- Copy and move semantics: Distinguish what happens when a value implements Copy from what happens when a non-Copy value is passed to a function that takes ownership, and explain why the difference can surprise beginners.
Why it matters: It explains why code that works with integers can fail when you replace them with a custom type.
Key terms: Copy trait, copy semantics, move semantics, primitive types
4.2 Guide to the figures in this chapter
4.3 What is an owner? Does it have any responsibilities?
This section explains that Rust ownership determines when values are cleaned up, while giving owners no special control over access or borrowing.
- Owner cleanup responsibility: Explain that an owner is responsible for cleanup when its values’ lifetimes end, and that the compiler ordinarily arranges the destructor calls.
Why it matters: It explains why ordinary Rust code usually does not call destructors explicitly. - Custom cleanup with Drop: Describe how a type can provide a custom destructor by implementing Drop, and what its drop(&mut self) method is for.
Why it matters: It identifies where a type puts cleanup that needs more than the usual automatic behavior. - Owner lifetime constraint: Explain why a value cannot outlive its owner and describe the consequence the section gives for reference-based structures such as trees.
Why it matters: It connects cleanup at the end of an owner’s lifetime to a practical constraint on data structures. - Ownership versus access control: Distinguish owning a value from having privileged access to it or authority over whether other code borrows it.
Why it matters: It prevents treating Rust’s technical meaning of ownership like exclusive personal property rights.
Key terms: owner, lifetime, destructor, Drop, drop(&mut self), custom destructor, references, ownership, borrowing
4.4 How ownership moves
This section shows how ownership of a CubeSat value moves through assignment, function arguments, and return values.
- Ownership transfer paths: Trace how ownership moves when a CubeSat value is assigned to another variable, passed to a function, or returned to its caller.
Why it matters: Following who owns a value helps explain what happens when it crosses a function boundary.
Key terms: ownership, assignment, function barrier, argument, return value
4.5 Resolving ownership issues
Introduces a CubeSat messaging example in which moving a satellite into a send call causes an ownership problem, then sets up types for the examples that follow.
- Satellite ownership after sending: Explain why moving sat_a into base.send() is a problem when main still needs to use the satellite.
Why it matters: This is the concrete ownership issue the following examples aim to resolve. - Messaging example data types: Describe how the example represents satellite messages and what must be created along with a CubeSat.
Why it matters: These types provide the data used in the ownership examples.
Key terms: ownership, sat_a, base.send(), CubeSat, Mailbox, Vec<Message>, Message, String
4.5.1 Use references where full ownership is not required
This section shows how borrowing a CubeSat instead of taking ownership lets ground-station methods access and modify it, while a sent message moves into its mailbox.
- Borrowing Instead of Ownership: Explain how a function can request only the access it needs: `&T` for read-only access or `&mut T` for read-write access, rather than taking ownership.
Why it matters: Choosing a borrow when ownership is unnecessary helps avoid ownership issues. - CubeSat Mailbox Access: Describe why `GroundStation.send()` and `CubeSat.recv()` need mutable access to a CubeSat, and what happens to ownership of a message when it is pushed into the mailbox.
Why it matters: The mailbox example shows that borrowing the satellite and moving the message are separate choices.
Key terms: borrow, ownership, & T, &mut T, GroundStation.send(), CubeSat.recv(), CubeSat.messages, messages.push(), mutable access
4.5.2 Use fewer long-lived values
This section resolves ownership difficulties in the CubeSat example by creating short-lived satellite objects and keeping their messages in a separate, long-lived mailbox.
- Short-lived satellite objects: Explain why the CubeSat design creates satellite objects when needed instead of keeping them alive throughout the program.
Why it matters: Changing how long objects live can simplify ownership without requiring every component to keep the same objects around. - Separate message storage: Explain why messages must be stored outside the short-lived CubeSat instances and how the instances find messages intended for them.
Why it matters: Persistent message storage lets satellite objects be temporary without losing their messages. - Mailbox-controlled message changes: Describe how the revised design handles changes to stored messages without letting satellites modify the mailbox’s internal data directly.
Why it matters: Giving the mailbox control of its own data makes the short-lived satellite design fit Rust’s mutable-borrow restriction.
Key terms: short-lived variables, CubeSat, ownership, Mailbox, Vec<Message>, sender, recipient, mutable borrow, message vector
4.5.3 Duplicate the value
This section presents duplication as an alternative to restructuring ownership and distinguishes Rust’s implicit Copy behavior from explicit Clone calls.
- Copy and Clone behavior: Distinguish how Copy and Clone duplicate values, and explain what happens when an assignment or function call would otherwise move a Copy value.
Why it matters: The distinction helps you recognize whether duplication happens automatically or because the programmer requested it. - Limits of automatic copying: Explain why Rust programmers do not use Copy for every type, including its expected low cost and the need for some types to use different duplication behavior.
Why it matters: Choosing a duplication mode makes its cost and behavior easier to reason about. - Copy implementation eligibility: Describe why implementing Copy is straightforward for a type whose contained types already implement Copy, and recognize that the section presents both derived and manual implementations.
Why it matters: The example connects Copy’s behavior to the kinds of compound types that can use it.
Key terms: Copy, Clone, .clone(), ownership, String, std::rc::Rc<T>, derive, impl blocks
4.5.4 Wrap data within specialty types
This section shows how wrapper types provide shared ownership and, when needed, mutation, while adding runtime costs and limits.
- Reference-counted shared ownership: Explain how Rc<T> lets multiple places share ownership of a value and how its reference count determines when the value is dropped.
Why it matters: Reference counting is an alternative when two places need ownership but duplicating the value would be expensive. - Shared interior mutability: Describe why Rc<RefCell<T>> is used instead of Rc<T> when shared owners need to modify a value, including what interior mutability means in this example.
Why it matters: It explains how the ground-station example modifies shared data despite an immutable variable. - Wrapper costs and thread limits: Explain the tradeoff of adding ownership or mutation behavior with wrappers, and state the section's caution about using Rc<T> in multithreaded code.
Why it matters: Choosing a wrapper means weighing its added capabilities against runtime cost and whether the code uses multiple threads.
Key terms: Rc<T>, shared ownership, reference counting, Clone, Drop, Rc<RefCell<T>>, interior mutability, immutable façade, shared read/write access, runtime cost
4.6 Summary
Where this is used later
- Raw pointers in Rust (chapter 6) · Assumes the distinction between owning a value and referring to one.
- Rust’s pointer ecosystem (chapter 6) · The earlier discussion of an owner’s relationship to a value.
- Analyzing the impact of dynamic memory allocation (chapter 6) · Rust’s ownership and lifetime responsibilities.
- Exploring Rust’s reference and pointer types (chapter 6) · Builds on the distinction between references and ownership when exploring Rust’s reference and pointer types.
- Smart pointer building blocks (chapter 6) · Provides an example of combining wrapper types to change how data can be accessed.
- Shared variables (chapter 10) · The thread-safety caution applies when working with shared variables across threads.
Concepts explained in this chapter
- ownershipAn owner is the binding or program entity responsible for cleaning up a value when its lifetime ends.
- Copy traitCopy is a Rust marker trait whose implementation permits values of a type to be implicitly duplicated rather than moved.
- borrowingBorrowing is Rust's mechanism for temporarily accessing a value through a reference without taking ownership of it.
- lifetimeA lifetime is the statically determined span during which a Rust value or reference remains valid.
- borrow checkerThe borrow checker is the Rust compiler component that verifies ownership, borrowing, and lifetime rules to prevent invalid memory access.
- moveMove semantics define how transferring ownership of a value invalidates or relocates its previous binding instead of implicitly copying the value.
- referenceA reference is a non-owning value that provides access to another value without transferring its ownership.
- scopeA scope is a region of program text that determines where names and declarations are visible.
- type safetyType safety is the enforcement of rules that prevent values of incompatible types from being used together in invalid operations.
- assignmentAssignment is an operation or expression that binds or updates a variable or other assignable place with a value, potentially transferring ownership when the value is non-copyable.
- copy semanticsCopy semantics are value-transfer rules in which assigning or passing a value duplicates it while leaving the original usable.
- interior mutabilityInterior mutability is a design property allowing a value's internal state to change through an otherwise immutable interface.
- enumAn enum is a type whose value is one of a finite set of named variants, optionally carrying associated data.
- integerAn integer is a whole-number value without a fractional component.
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.
