diff --git a/src/ch15-00-smart-pointers.md b/src/ch15-00-smart-pointers.md index cadd5478b..c38ba3e84 100644 --- a/src/ch15-00-smart-pointers.md +++ b/src/ch15-00-smart-pointers.md @@ -5,39 +5,36 @@ memory. This address refers to, or “points at,” some other data. The most common kind of pointer in Rust is a reference, which you learned about in Chapter 4. References are indicated by the `&` symbol and borrow the value they point to. They don’t have any special capabilities other than referring to -data. Also, they don’t have any overhead and are the kind of pointer we use -most often. +data, and have no overhead. -*Smart pointers*, on the other hand, are data structures that not only act like -a pointer but also have additional metadata and capabilities. The concept of +*Smart pointers*, on the other hand, are data structures that act like a +pointer but also have additional metadata and capabilities. The concept of smart pointers isn’t unique to Rust: smart pointers originated in C++ and exist -in other languages as well. In Rust, the different smart pointers defined in -the standard library provide functionality beyond that provided by references. -One example that we’ll explore in this chapter is the *reference counting* -smart pointer type. This pointer enables you to have multiple owners of data by -keeping track of the number of owners and, when no owners remain, cleaning up -the data. +in other languages as well. Rust has a variety of smart pointers defined in the +standard library that provide functionality beyond that provided by references. +To explore the general concept, we'll look at a couple of different examples of +smart pointers, including a *reference counting* smart pointer type. This +pointer enables you to allow data to have multiple owners by keeping track of +the number of owners and, when no owners remain, cleaning up the data. -In Rust, which uses the concept of ownership and borrowing, an additional -difference between references and smart pointers is that references are -pointers that only borrow data; in contrast, in many cases, smart pointers -*own* the data they point to. +Rust, with its concept of ownership and borrowing, has an additional difference +between references and smart pointers: while references only borrow data, in +many cases, smart pointers *own* the data they point to. -We’ve already encountered a few smart pointers in this book, such as `String` -and `Vec` in Chapter 8, although we didn’t call them smart pointers at the -time. Both these types count as smart pointers because they own some memory and -allow you to manipulate it. They also have metadata (such as their capacity) -and extra capabilities or guarantees (such as with `String` ensuring its data -will always be valid UTF-8). +Though we didn't call them as much at the time, we’ve already encountered a few +smart pointers in this book, including `String` and `Vec` in Chapter 8. Both +these types count as smart pointers because they own some memory and allow you +to manipulate it. They also have metadata and extra capabilities or guarantees. +`String`, for example, stores its capacity as metadata and has the extra +ability to ensure its data will always be valid UTF-8. -Smart pointers are usually implemented using structs. The characteristic that -distinguishes a smart pointer from an ordinary struct is that smart pointers -implement the `Deref` and `Drop` traits. The `Deref` trait allows an instance -of the smart pointer struct to behave like a reference so you can write code -that works with either references or smart pointers. The `Drop` trait allows -you to customize the code that is run when an instance of the smart pointer -goes out of scope. In this chapter, we’ll discuss both traits and demonstrate -why they’re important to smart pointers. +Smart pointers are usually implemented using structs. Unlike an ordinary +struct, smart pointers implement the `Deref` and `Drop` traits. The `Deref` +trait allows an instance of the smart pointer struct to behave like a reference +so you can write your code to work with either references or smart pointers. +The `Drop` trait allows you to customize the code that's run when an instance +of the smart pointer goes out of scope. In this chapter, we’ll discuss both +traits and demonstrate why they’re important to smart pointers. Given that the smart pointer pattern is a general design pattern used frequently in Rust, this chapter won’t cover every existing smart pointer. Many diff --git a/src/ch15-01-box.md b/src/ch15-01-box.md index 5dc82a97f..0166d6abd 100644 --- a/src/ch15-01-box.md +++ b/src/ch15-01-box.md @@ -30,8 +30,8 @@ Chapter 17! ### Using a `Box` to Store Data on the Heap -Before we discuss this use case for `Box`, we’ll cover the syntax and how to -interact with values stored within a `Box`. +Before we discuss the heap storage use case for `Box`, we’ll cover the +syntax and how to interact with values stored within a `Box`. Listing 15-1 shows how to use a box to store an `i32` value on the heap: @@ -49,8 +49,8 @@ value `5`, which is allocated on the heap. This program will print `b = 5`; in this case, we can access the data in the box similar to how we would if this data were on the stack. Just like any owned value, when a box goes out of scope, as `b` does at the end of `main`, it will be deallocated. The -deallocation happens for the box (stored on the stack) and the data it points -to (stored on the heap). +deallocation happens both for the box (stored on the stack) and the data it +points to (stored on the heap). Putting a single value on the heap isn’t very useful, so you won’t use boxes by themselves in this way very often. Having values like a single `i32` on the @@ -60,12 +60,12 @@ wouldn’t be allowed to if we didn’t have boxes. ### Enabling Recursive Types with Boxes -At compile time, Rust needs to know how much space a type takes up. One type -whose size can’t be known at compile time is a *recursive type*, where a value -can have as part of itself another value of the same type. Because this nesting -of values could theoretically continue infinitely, Rust doesn’t know how much -space a value of a recursive type needs. However, boxes have a known size, so -by inserting a box in a recursive type definition, you can have recursive types. +A value of *recursive type* can have another value of the same type as part of +itself. Recursive types pose an issue because at compile time Rust needs to +know how much space a type takes up. However, the nesting of values of +recursive types could theoretically continue infinitely, so Rust can’t know how +much space the value needs. Because boxes have a known size, we can enable +recursive types by inserting a box in the recursive type definition. As an example of a recursive type, let’s explore the *cons list*. This is a data type commonly found in functional programming languages. The cons list type @@ -76,14 +76,17 @@ more complex situations involving recursive types. #### More Information About the Cons List A *cons list* is a data structure that comes from the Lisp programming language -and its dialects. In Lisp, the `cons` function (short for “construct function”) -constructs a new pair from its two arguments, which usually are a single value -and another pair. These pairs containing pairs form a list. +and its dialects and is made up of nested pairs. Its name comes from the `cons` +function (short for “construct function”) in Lisp that constructs a new pair +from its two arguments. By calling `cons` on a pair consisting of a value and +another pair, we can construct cons lists made up of recursive pairs. -The cons function concept has made its way into more general functional -programming jargon: “to cons *x* onto *y*” informally means to construct a new -container instance by putting the element *x* at the start of this new -container, followed by the container *y*. +For example, here's a pseudocode representation of a cons list containing the +list 1, 2, 3 with each pair in parentheses: + +```text +(1, (2, (3, Nil))) +``` Each item in a cons list contains two elements: the value of the current item and the next item. The last item in the list contains only a value called `Nil` @@ -92,12 +95,11 @@ function. The canonical name to denote the base case of the recursion is `Nil`. Note that this is not the same as the “null” or “nil” concept in Chapter 6, which is an invalid or absent value. -Although functional programming languages use cons lists frequently, the cons -list isn’t a commonly used data structure in Rust. Most of the time when you -have a list of items in Rust, `Vec` is a better choice to use. Other, more -complex recursive data types *are* useful in various situations, but by -starting with the cons list, we can explore how boxes let us define a recursive -data type without much distraction. +The cons list isn’t a commonly used data structure in Rust. Most of the time +when you have a list of items in Rust, `Vec` is a better choice to use. +Other, more complex recursive data types *are* useful in various situations, +but by starting with the cons list in this chapter, we can explore how boxes +let us define a recursive data type without much distraction. Listing 15-2 contains an enum definition for a cons list. Note that this code won’t compile yet because the `List` type doesn’t have a known size, which @@ -147,9 +149,8 @@ a recursive enum The error shows this type “has infinite size.” The reason is that we’ve defined `List` with a variant that is recursive: it holds another value of itself directly. As a result, Rust can’t figure out how much space it needs to store a -`List` value. Let’s break down why we get this error a bit. First, let’s look -at how Rust decides how much space it needs to store a value of a non-recursive -type. +`List` value. Let’s break down why we get this error. First, we'll look at how +Rust decides how much space it needs to store a value of a non-recursive type. #### Computing the Size of a Non-Recursive Type @@ -183,9 +184,8 @@ variant. The `Cons` variant holds a value of type `i32` and a value of type #### Using `Box` to Get a Recursive Type with a Known Size -Rust can’t figure out how much space to allocate for recursively defined types, -so the compiler gives the error in Listing 15-4. But the error does include -this helpful suggestion: +Because Rust can’t figure out how much space to allocate for recursively +defined types, the compiler gives an error with this helpful suggestion: + + +### Following the Pointer to the Value A regular reference is a type of pointer, and one way to think of a pointer is as an arrow to a value stored somewhere else. In Listing 15-6, we create a reference to an `i32` value and then use the dereference operator to follow the -reference to the data: +reference to the value: Filename: src/main.rs @@ -35,12 +38,12 @@ reference to the data: Listing 15-6: Using the dereference operator to follow a reference to an `i32` value -The variable `x` holds an `i32` value, `5`. We set `y` equal to a reference to +The variable `x` holds an `i32` value `5`. We set `y` equal to a reference to `x`. We can assert that `x` is equal to `5`. However, if we want to make an assertion about the value in `y`, we have to use `*y` to follow the reference -to the value it’s pointing to (hence *dereference*). Once we dereference `y`, -we have access to the integer value `y` is pointing to that we can compare with -`5`. +to the value it’s pointing to (hence *dereference*) so the compiler can compare +the actual value. Once we dereference `y`, we have access to the integer value +`y` is pointing to that we can compare with `5`. If we tried to write `assert_eq!(5, y);` instead, we would get this compilation error: @@ -56,7 +59,9 @@ to the value it’s pointing to. ### Using `Box` Like a Reference We can rewrite the code in Listing 15-6 to use a `Box` instead of a -reference; the dereference operator will work as shown in Listing 15-7: +reference; the dereference operator used on the `Box` in Listing 15-7 +functions in the same way as the dereference operator used on the reference in +Listing 15-6: Filename: src/main.rs @@ -139,13 +144,13 @@ contains an implementation of `Deref` to add to the definition of `MyBox`: Listing 15-10: Implementing `Deref` on `MyBox` -The `type Target = T;` syntax defines an associated type for the `Deref` trait -to use. Associated types are a slightly different way of declaring a generic -parameter, but you don’t need to worry about them for now; we’ll cover them in -more detail in Chapter 19. +The `type Target = T;` syntax defines an associated type for the `Deref` +trait to use. Associated types are a slightly different way of declaring a +generic parameter, but you don’t need to worry about them for now; we’ll cover +them in more detail in Chapter 19. We fill in the body of the `deref` method with `&self.0` so `deref` returns a -reference to the value we want to access with the `*` operator. Recall from the +reference to the value we want to access with the `*` operator; recall from the [“Using Tuple Structs without Named Fields to Create Different Types”][tuple-structs] section of Chapter 5 that `.0` accesses the first value in a tuple struct. The `main` function in Listing 15-9 that @@ -169,12 +174,12 @@ call the `deref` method. This Rust feature lets us write code that functions identically whether we have a regular reference or a type that implements `Deref`. -The reason the `deref` method returns a reference to a value, and that the plain -dereference outside the parentheses in `*(y.deref())` is still necessary, is the -ownership system. If the `deref` method returned the value directly instead of -a reference to the value, the value would be moved out of `self`. We don’t want -to take ownership of the inner value inside `MyBox` in this case or in most -cases where we use the dereference operator. +The reason the `deref` method returns a reference to a value, and that the +plain dereference outside the parentheses in `*(y.deref())` is still necessary, +is to do with the ownership system. If the `deref` method returned the value +directly instead of a reference to the value, the value would be moved out of +`self`. We don’t want to take ownership of the inner value inside `MyBox` in +this case or in most cases where we use the dereference operator. Note that the `*` operator is replaced with a call to the `deref` method and then a call to the `*` operator just once, each time we use a `*` in our code. @@ -184,15 +189,15 @@ Listing 15-9. ### Implicit Deref Coercions with Functions and Methods -*Deref coercion* is a convenience that Rust performs on arguments to functions -and methods. Deref coercion works only on types that implement the `Deref` -trait. Deref coercion converts a reference to such a type into a reference to -another type. For example, deref coercion can convert `&String` to `&str` -because `String` implements the `Deref` trait such that it returns `&str`. -Deref coercion happens automatically when we pass a reference to a particular -type’s value as an argument to a function or method that doesn’t match the -parameter type in the function or method definition. A sequence of calls to the -`deref` method converts the type we provided into the type the parameter needs. +*Deref coercion* converts a reference to a type that implements the `Deref` +trait into a reference to another type. For example, deref coercion can convert +`&String` to `&str` because `String` implements the `Deref` trait such that it +returns `&str`. Deref conversion is a convenience Rust performs on arguments to +functions and methods, and works only on types that implement the `Deref` +trait. It happens automatically when we pass a reference to a particular type’s +value as an argument to a function or method that doesn’t match the parameter +type in the function or method definition. A sequence of calls to the `deref` +method converts the type we provided into the type the parameter needs. Deref coercion was added to Rust so that programmers writing function and method calls don’t need to add as many explicit references and dereferences @@ -249,7 +254,7 @@ didn’t have deref coercion The `(*m)` dereferences the `MyBox` into a `String`. Then the `&` and `[..]` take a string slice of the `String` that is equal to the whole string to -match the signature of `hello`. The code without deref coercions is harder to +match the signature of `hello`. This code without deref coercions is harder to read, write, and understand with all of these symbols involved. Deref coercion allows Rust to handle these conversions for us automatically. @@ -272,10 +277,10 @@ cases: * From `&mut T` to `&mut U` when `T: DerefMut` * From `&mut T` to `&U` when `T: Deref` -The first two cases are the same except for mutability. The first case states -that if you have a `&T`, and `T` implements `Deref` to some type `U`, you can -get a `&U` transparently. The second case states that the same deref coercion -happens for mutable references. +The first two cases are the same as each other except that the second +implements mutability. The first case states that if you have a `&T`, and `T` +implements `Deref` to some type `U`, you can get a `&U` transparently. The +second case states that the same deref coercion happens for mutable references. The third case is trickier: Rust will also coerce a mutable reference to an immutable one. But the reverse is *not* possible: immutable references will diff --git a/src/ch15-03-drop.md b/src/ch15-03-drop.md index d814db93d..fcc7dc491 100644 --- a/src/ch15-03-drop.md +++ b/src/ch15-03-drop.md @@ -2,12 +2,12 @@ The second trait important to the smart pointer pattern is `Drop`, which lets you customize what happens when a value is about to go out of scope. You can -provide an implementation for the `Drop` trait on any type, and the code you -specify can be used to release resources like files or network connections. -We’re introducing `Drop` in the context of smart pointers because the -functionality of the `Drop` trait is almost always used when implementing a -smart pointer. For example, when a `Box` is dropped it will deallocate the -space on the heap that the box points to. +provide an implementation for the `Drop` trait on any type, and that code can +be used to release resources like files or network connections. We’re +introducing `Drop` in the context of smart pointers because the functionality +of the `Drop` trait is almost always used when implementing a smart pointer. +For example, when a `Box` is dropped it will deallocate the space on the +heap that the box points to. In some languages, for some types, the programmer must call code to free memory or resources every time they finish using an instance those types. Examples @@ -18,15 +18,14 @@ this code automatically. As a result, you don’t need to be careful about placing cleanup code everywhere in a program that an instance of a particular type is finished with—you still won’t leak resources! -Specify the code to run when a value goes out of scope by implementing the +You specify the code to run when a value goes out of scope by implementing the `Drop` trait. The `Drop` trait requires you to implement one method named `drop` that takes a mutable reference to `self`. To see when Rust calls `drop`, let’s implement `drop` with `println!` statements for now. Listing 15-14 shows a `CustomSmartPointer` struct whose only custom functionality is that it will print `Dropping CustomSmartPointer!` when the -instance goes out of scope. This example demonstrates when Rust runs the `drop` -function. +instance goes out of scope, to show when Rust runs the `drop` function. Filename: src/main.rs @@ -42,7 +41,7 @@ scope. We implement the `Drop` trait on `CustomSmartPointer` and provide an implementation for the `drop` method that calls `println!`. The body of the `drop` function is where you would place any logic that you wanted to run when an instance of your type goes out of scope. We’re printing some text here to -demonstrate when Rust will call `drop`. +demonstrate visually when Rust will call `drop`. In `main`, we create two instances of `CustomSmartPointer` and then print `CustomSmartPointers created`. At the end of `main`, our instances of @@ -58,9 +57,10 @@ When we run this program, we’ll see the following output: Rust automatically called `drop` for us when our instances went out of scope, calling the code we specified. Variables are dropped in the reverse order of -their creation, so `d` was dropped before `c`. This example gives you a visual -guide to how the `drop` method works; usually you would specify the cleanup -code that your type needs to run rather than a print message. +their creation, so `d` was dropped before `c`. This example's purpose is to +give you a visual guide to how the `drop` method works; usually you would +specify the cleanup code that your type needs to run rather than a print +message. ### Dropping a Value Early with `std::mem::drop` @@ -100,18 +100,18 @@ for a function that cleans up an instance. A *destructor* is analogous to a particular destructor. Rust doesn’t let us call `drop` explicitly because Rust would still -automatically call `drop` on the value at the end of `main`. This would be a +automatically call `drop` on the value at the end of `main`. This would cause a *double free* error because Rust would be trying to clean up the same value twice. We can’t disable the automatic insertion of `drop` when a value goes out of scope, and we can’t call the `drop` method explicitly. So, if we need to force -a value to be cleaned up early, we can use the `std::mem::drop` function. +a value to be cleaned up early, we use the `std::mem::drop` function. The `std::mem::drop` function is different from the `drop` method in the `Drop` -trait. We call it by passing the value we want to force to be dropped early as -an argument. The function is in the prelude, so we can modify `main` in Listing -15-15 to call the `drop` function, as shown in Listing 15-16: +trait. We call it by passing as an argument the value we want to force drop. +The function is in the prelude, so we can modify `main` in Listing 15-15 to +call the `drop` function, as shown in Listing 15-16: Filename: src/main.rs diff --git a/src/ch15-04-rc.md b/src/ch15-04-rc.md index cea847509..87a42eb1a 100644 --- a/src/ch15-04-rc.md +++ b/src/ch15-04-rc.md @@ -5,13 +5,13 @@ owns a given value. However, there are cases when a single value might have multiple owners. For example, in graph data structures, multiple edges might point to the same node, and that node is conceptually owned by all of the edges that point to it. A node shouldn’t be cleaned up unless it doesn’t have any -edges pointing to it. +edges pointing to it and so has no owners. -To enable multiple ownership, Rust has a type called `Rc`, which is an -abbreviation for *reference counting*. The `Rc` type keeps track of the -number of references to a value to determine whether or not the value is still -in use. If there are zero references to a value, the value can be cleaned up -without any references becoming invalid. +You have to enable multiple ownership explicitly by using the Rust type +`Rc`, which is an abbreviation for *reference counting*. The `Rc` type +keeps track of the number of references to a value to determine whether or not +the value is still in use. If there are zero references to a value, the value +can be cleaned up without any references becoming invalid. Imagine `Rc` as a TV in a family room. When one person enters to watch TV, they turn it on. Others can come into the room and watch the TV. When the last @@ -127,9 +127,9 @@ then we can see how the reference count changes when `c` goes out of scope. Listing 15-19: Printing the reference count At each point in the program where the reference count changes, we print the -reference count, which we can get by calling the `Rc::strong_count` function. -This function is named `strong_count` rather than `count` because the `Rc` -type also has a `weak_count`; we’ll see what `weak_count` is used for in the +reference count, which we get by calling the `Rc::strong_count` function. This +function is named `strong_count` rather than `count` because the `Rc` type +also has a `weak_count`; we’ll see what `weak_count` is used for in the [“Preventing Reference Cycles: Turning an `Rc` into a `Weak`”][preventing-ref-cycles] section. @@ -148,9 +148,9 @@ automatically when an `Rc` value goes out of scope. What we can’t see in this example is that when `b` and then `a` go out of scope at the end of `main`, the count is then 0, and the `Rc` is cleaned up -completely at that point. Using `Rc` allows a single value to have -multiple owners, and the count ensures that the value remains valid as long as -any of the owners still exist. +completely. Using `Rc` allows a single value to have multiple owners, and +the count ensures that the value remains valid as long as any of the owners +still exist. Via immutable references, `Rc` allows you to share data between multiple parts of your program for reading only. If `Rc` allowed you to have multiple diff --git a/src/ch15-05-interior-mutability.md b/src/ch15-05-interior-mutability.md index a42b49fa6..7573c0c8f 100644 --- a/src/ch15-05-interior-mutability.md +++ b/src/ch15-05-interior-mutability.md @@ -4,11 +4,13 @@ data even when there are immutable references to that data; normally, this action is disallowed by the borrowing rules. To mutate data, the pattern uses `unsafe` code inside a data structure to bend Rust’s usual rules that govern -mutation and borrowing. We haven’t yet covered unsafe code; we will in Chapter -19. We can use types that use the interior mutability pattern when we can -ensure that the borrowing rules will be followed at runtime, even though the -compiler can’t guarantee that. The `unsafe` code involved is then wrapped in a -safe API, and the outer type is still immutable. +mutation and borrowing. We haven’t yet covered unsafe code that indicates we're +checking the rules manually instead of the compiler checking them for us; we +will discuss unsafe code more in Chapter 19. We can use types that use the +interior mutability pattern only when we can ensure that the borrowing rules +will be followed at runtime, even though the compiler can’t guarantee that. The +`unsafe` code involved is then wrapped in a safe API, and the outer type is +still immutable. Let’s explore this concept by looking at the `RefCell` type that follows the interior mutability pattern. @@ -19,8 +21,8 @@ Unlike `Rc`, the `RefCell` type represents single ownership over the data it holds. So, what makes `RefCell` different from a type like `Box`? Recall the borrowing rules you learned in Chapter 4: -* At any given time, you can have *either* (but not both of) one mutable - reference or any number of immutable references. +* At any given time, you can have *either* (but not both) one mutable reference + or any number of immutable references. * References must always be valid. With references and `Box`, the borrowing rules’ invariants are enforced at @@ -35,11 +37,11 @@ reasons, checking the borrowing rules at compile time is the best choice in the majority of cases, which is why this is Rust’s default. The advantage of checking the borrowing rules at runtime instead is that -certain memory-safe scenarios are then allowed, whereas they are disallowed by -the compile-time checks. Static analysis, like the Rust compiler, is inherently -conservative. Some properties of code are impossible to detect by analyzing the -code: the most famous example is the Halting Problem, which is beyond the scope -of this book but is an interesting topic to research. +certain memory-safe scenarios are then allowed, where they would’ve been +disallowed by the compile-time checks. Static analysis, like the Rust compiler, +is inherently conservative. Some properties of code are impossible to detect by +analyzing the code: the most famous example is the Halting Problem, which is +beyond the scope of this book but is an interesting topic to research. Because some analysis is impossible, if the Rust compiler can’t be sure the code complies with the ownership rules, it might reject a correct program; in @@ -88,7 +90,7 @@ If you tried to compile this code, you’d get the following error: However, there are situations in which it would be useful for a value to mutate itself in its methods but appear immutable to other code. Code outside the value’s methods would not be able to mutate the value. Using `RefCell` is -one way to get the ability to have interior mutability. But `RefCell` +one way to get the ability to have interior mutability, but `RefCell` doesn’t get around the borrowing rules completely: the borrow checker in the compiler allows this interior mutability, and the borrowing rules are checked at runtime instead. If you violate the rules, you’ll get a `panic!` instead of @@ -99,8 +101,12 @@ an immutable value and see why that is useful. #### A Use Case for Interior Mutability: Mock Objects -A *test double* is the general programming concept for a type used in place of -another type during testing. *Mock objects* are specific types of test doubles +Sometimes during testing a programmer will use a type in place of another type, +in order to observe particular behavior and assert it's implemented correctly. +This placeholder type is called a *test double*. Think of it in the sense of a +"stunt double" in filmmaking, where a person steps in and substitutes for an +actor to do a particular tricky scene. Test doubles stand in for other types +when we're running tests. *Mock objects* are specific types of test doubles that record what happens during a test so you can assert that the correct actions took place. @@ -209,9 +215,9 @@ instance around the empty vector. For the implementation of the `send` method, the first parameter is still an immutable borrow of `self`, which matches the trait definition. We call `borrow_mut` on the `RefCell>` in `self.sent_messages` to get a -mutable reference to the value inside the `RefCell>`, which is -the vector. Then we can call `push` on the mutable reference to the vector to -keep track of the messages sent during the test. +mutable reference to the value inside the `RefCell>`, which is the +vector. Then we can call `push` on the mutable reference to the vector to keep +track of the messages sent during the test. The last change we have to make is in the assertion: to see how many items are in the inner vector, we call `borrow` on the `RefCell>` to get an @@ -265,15 +271,16 @@ Notice that the code panicked with the message `already borrowed: BorrowMutError`. This is how `RefCell` handles violations of the borrowing rules at runtime. -Catching borrowing errors at runtime rather than compile time means that you -would find a mistake in your code later in the development process and possibly -not until your code was deployed to production. Also, your code would incur a -small runtime performance penalty as a result of keeping track of the borrows -at runtime rather than compile time. However, using `RefCell` makes it -possible to write a mock object that can modify itself to keep track of the -messages it has seen while you’re using it in a context where only immutable -values are allowed. You can use `RefCell` despite its trade-offs to get more -functionality than regular references provide. +Choosing to catch borrowing errors at runtime rather than compile time, as +we've done here, means you'd potentially be finding mistakes in your code later +in the development process: possibly not until your code was deployed to +production. Also, your code would incur a small runtime performance penalty as +a result of keeping track of the borrows at runtime rather than compile time. +However, using `RefCell` makes it possible to write a mock object that can +modify itself to keep track of the messages it has seen while you’re using it +in a context where only immutable values are allowed. You can use `RefCell` +despite its trade-offs to get more functionality than regular references +provide. ### Having Multiple Owners of Mutable Data by Combining `Rc` and `RefCell` @@ -309,8 +316,8 @@ than transferring ownership from `value` to `a` or having `a` borrow from We wrap the list `a` in an `Rc` so when we create lists `b` and `c`, they can both refer to `a`, which is what we did in Listing 15-18. -After we’ve created the lists in `a`, `b`, and `c`, we add 10 to the value in -`value`. We do this by calling `borrow_mut` on `value`, which uses the +After we’ve created the lists in `a`, `b`, and `c`, we want to add 10 to the +value in `value`. We do this by calling `borrow_mut` on `value`, which uses the automatic dereferencing feature we discussed in Chapter 5 (see the section [“Where’s the `->` Operator?”][wheres-the---operator]) to dereference the `Rc` to the inner `RefCell` value. The `borrow_mut` diff --git a/src/ch15-06-reference-cycles.md b/src/ch15-06-reference-cycles.md index 0ad8044cf..89665f865 100644 --- a/src/ch15-06-reference-cycles.md +++ b/src/ch15-06-reference-cycles.md @@ -47,15 +47,15 @@ reference counts are at various points in this process. values pointing to each other We create an `Rc` instance holding a `List` value in the variable `a` -with an initial list of `5, Nil`. We then create an `Rc` instance -holding another `List` value in the variable `b` that contains the value 10 and -points to the list in `a`. +with an initial list of `5, Nil`. We then create an `Rc` instance holding +another `List` value in the variable `b` that contains the value 10 and points +to the list in `a`. -We modify `a` so it points to `b` instead of `Nil`, creating a cycle. We -do that by using the `tail` method to get a reference to the -`RefCell>` in `a`, which we put in the variable `link`. Then we use -the `borrow_mut` method on the `RefCell>` to change the value inside -from an `Rc` that holds a `Nil` value to the `Rc` in `b`. +We modify `a` so it points to `b` instead of `Nil`, creating a cycle. We do +that by using the `tail` method to get a reference to the `RefCell>` +in `a`, which we put in the variable `link`. Then we use the `borrow_mut` +method on the `RefCell>` to change the value inside from an `Rc` +that holds a `Nil` value to the `Rc` in `b`. When we run this code, keeping the last `println!` commented out for the moment, we’ll get this output: @@ -84,11 +84,12 @@ If you uncomment the last `println!` and run the program, Rust will try to print this cycle with `a` pointing to `b` pointing to `a` and so forth until it overflows the stack. -In this case, right after we create the reference cycle, the program ends. The -consequences of this cycle aren’t very dire. However, if a more complex program -allocated lots of memory in a cycle and held onto it for a long time, the -program would use more memory than it needed and might overwhelm the system, -causing it to run out of available memory. +Compared to a real-world program, the consequences creating a reference cycle +in this example aren’t very dire: right after we create the reference cycle, +the program ends. However, if a more complex program allocated lots of memory +in a cycle and held onto it for a long time, the program would use more memory +than it needed and might overwhelm the system, causing it to run out of +available memory. Creating reference cycles is not easily done, but it’s not impossible either. If you have `RefCell` values that contain `Rc` values or similar nested @@ -114,17 +115,18 @@ So far, we’ve demonstrated that calling `Rc::clone` increases the `strong_count` of an `Rc` instance, and an `Rc` instance is only cleaned up if its `strong_count` is 0. You can also create a *weak reference* to the value within an `Rc` instance by calling `Rc::downgrade` and passing a -reference to the `Rc`. When you call `Rc::downgrade`, you get a smart -pointer of type `Weak`. Instead of increasing the `strong_count` in the -`Rc` instance by 1, calling `Rc::downgrade` increases the `weak_count` by 1. -The `Rc` type uses `weak_count` to keep track of how many `Weak` -references exist, similar to `strong_count`. The difference is the `weak_count` -doesn’t need to be 0 for the `Rc` instance to be cleaned up. +reference to the `Rc`. Strong references are how you can share ownership of +an `Rc` instance. Weak references don’t express an ownership relationship, +and their count doesn't affect when an `Rc` instance is cleaned up. They +won’t cause a reference cycle because any cycle involving some weak references +will be broken once the strong reference count of values involved is 0. -Strong references are how you can share ownership of an `Rc` instance. Weak -references don’t express an ownership relationship. They won’t cause a -reference cycle because any cycle involving some weak references will be broken -once the strong reference count of values involved is 0. +When you call `Rc::downgrade`, you get a smart pointer of type `Weak`. +Instead of increasing the `strong_count` in the `Rc` instance by 1, calling +`Rc::downgrade` increases the `weak_count` by 1. The `Rc` type uses +`weak_count` to keep track of how many `Weak` references exist, similar to +`strong_count`. The difference is the `weak_count` doesn’t need to be 0 for the +`Rc` instance to be cleaned up. Because the value that `Weak` references might have been dropped, to do anything with the value that a `Weak` is pointing to, you must make sure the @@ -214,9 +216,9 @@ node will have a way to refer to its parent, `branch`: Listing 15-28: A `leaf` node with a weak reference to its parent node `branch` -Creating the `leaf` node looks similar to how creating the `leaf` node looked -in Listing 15-27 with the exception of the `parent` field: `leaf` starts out -without a parent, so we create a new, empty `Weak` reference instance. +Creating the `leaf` node looks similar to Listing 15-27 with the exception of +the `parent` field: `leaf` starts out without a parent, so we create a new, +empty `Weak` reference instance. At this point, when we try to get a reference to the parent of `leaf` by using the `upgrade` method, we get a `None` value. We see this in the output from the