Skip to content

Commit f8efec5

Browse files
authored
Merge pull request #4159 from rust-lang/ch21-vec-drain
Ch. 21: Use `Vec::drain` to teach alternatives to `Option`
2 parents 25a47dd + bfd6016 commit f8efec5

File tree

11 files changed

+46
-83
lines changed

11 files changed

+46
-83
lines changed

listings/ch21-web-server/listing-21-23/src/lib.rs

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -61,20 +61,18 @@ impl Drop for ThreadPool {
6161
fn drop(&mut self) {
6262
drop(self.sender.take());
6363

64-
for worker in &mut self.workers {
64+
for worker in self.workers.drain(..) {
6565
println!("Shutting down worker {}", worker.id);
6666

67-
if let Some(thread) = worker.thread.take() {
68-
thread.join().unwrap();
69-
}
67+
worker.thread.join().unwrap();
7068
}
7169
}
7270
}
7371
// ANCHOR_END: here
7472

7573
struct Worker {
7674
id: usize,
77-
thread: Option<thread::JoinHandle<()>>,
75+
thread: thread::JoinHandle<()>,
7876
}
7977

8078
impl Worker {
@@ -87,9 +85,6 @@ impl Worker {
8785
job();
8886
});
8987

90-
Worker {
91-
id,
92-
thread: Some(thread),
93-
}
88+
Worker { id, thread }
9489
}
9590
}

listings/ch21-web-server/listing-21-24/src/lib.rs

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -51,19 +51,17 @@ impl Drop for ThreadPool {
5151
fn drop(&mut self) {
5252
drop(self.sender.take());
5353

54-
for worker in &mut self.workers {
54+
for worker in self.workers.drain(..) {
5555
println!("Shutting down worker {}", worker.id);
5656

57-
if let Some(thread) = worker.thread.take() {
58-
thread.join().unwrap();
59-
}
57+
worker.thread.join().unwrap();
6058
}
6159
}
6260
}
6361

6462
struct Worker {
6563
id: usize,
66-
thread: Option<thread::JoinHandle<()>>,
64+
thread: thread::JoinHandle<()>,
6765
}
6866

6967
// ANCHOR: here
@@ -85,10 +83,7 @@ impl Worker {
8583
}
8684
});
8785

88-
Worker {
89-
id,
90-
thread: Some(thread),
91-
}
86+
Worker { id, thread }
9287
}
9388
}
9489
// ANCHOR_END: here

listings/ch21-web-server/listing-21-25/src/lib.rs

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -51,19 +51,17 @@ impl Drop for ThreadPool {
5151
fn drop(&mut self) {
5252
drop(self.sender.take());
5353

54-
for worker in &mut self.workers {
54+
for worker in self.workers.drain(..) {
5555
println!("Shutting down worker {}", worker.id);
5656

57-
if let Some(thread) = worker.thread.take() {
58-
thread.join().unwrap();
59-
}
57+
worker.thread.join().unwrap();
6058
}
6159
}
6260
}
6361

6462
struct Worker {
6563
id: usize,
66-
thread: Option<thread::JoinHandle<()>>,
64+
thread: thread::JoinHandle<()>,
6765
}
6866

6967
impl Worker {
@@ -84,9 +82,6 @@ impl Worker {
8482
}
8583
});
8684

87-
Worker {
88-
id,
89-
thread: Some(thread),
90-
}
85+
Worker { id, thread }
9186
}
9287
}

listings/ch21-web-server/no-listing-04-update-worker-definition/src/lib.rs renamed to listings/ch21-web-server/no-listing-04-update-drop-definition/src/lib.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,22 +44,22 @@ impl ThreadPool {
4444
}
4545
}
4646

47+
// ANCHOR: here
4748
impl Drop for ThreadPool {
4849
fn drop(&mut self) {
49-
for worker in &mut self.workers {
50+
for worker in self.workers.drain(..) {
5051
println!("Shutting down worker {}", worker.id);
5152

5253
worker.thread.join().unwrap();
5354
}
5455
}
5556
}
57+
// ANCHOR_END: here
5658

57-
// ANCHOR: here
5859
struct Worker {
5960
id: usize,
60-
thread: Option<thread::JoinHandle<()>>,
61+
thread: thread::JoinHandle<()>,
6162
}
62-
// ANCHOR_END: here
6363

6464
impl Worker {
6565
fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {

src/ch21-03-graceful-shutdown-and-cleanup.md

Lines changed: 30 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -47,63 +47,41 @@ Here is the error we get when we compile this code:
4747
{{#include ../listings/ch21-web-server/listing-21-22/output.txt}}
4848
```
4949

50-
The error tells us we can’t call `join` because we only have a mutable borrow
51-
of each `worker` and `join` takes ownership of its argument. To solve this
52-
issue, we need to move the thread out of the `Worker` instance that owns
53-
`thread` so `join` can consume the thread. We did this in Listing 17-15: if
54-
`Worker` holds an `Option<thread::JoinHandle<()>>` instead, we can call the
55-
`take` method on the `Option` to move the value out of the `Some` variant and
56-
leave a `None` variant in its place. In other words, a `Worker` that is running
57-
will have a `Some` variant in `thread`, and when we want to clean up a
58-
`Worker`, we’ll replace `Some` with `None` so the `Worker` doesn’t have a
59-
thread to run.
60-
61-
So we know we want to update the definition of `Worker` like this:
50+
The error tells us we can’t call `join` because we only have a mutable borrow of
51+
each `worker` and `join` takes ownership of its argument. To solve this issue,
52+
we need to move the thread out of the `Worker` instance that owns `thread` so
53+
`join` can consume the thread. One way to do this is by taking the same approach
54+
we did in Listing 18-15. If `Worker` held an `Option<thread::JoinHandle<()>>`,
55+
we could call the `take` method on the `Option` to move the value out of the
56+
`Some` variant and leave a `None` variant in its place. In other words, a
57+
`Worker` that is running would have a `Some` variant in `thread`, and when we
58+
wanted to clean up a `Worker`, we would replace `Some` with `None` so the
59+
`Worker` doesn’t have a thread to run.
60+
61+
However, the _only_ time this would come up would be when dropping the `Worker`.
62+
In exchange, we would have to deal with an `Option<thread::JoinHandle<()>>`
63+
everywhere we access `worker.thread`. Idiomatic Rust uses `Option` quite a bit,
64+
but when you find yourself wrapping something in `Option` as a workaround even
65+
though you know the item will always be present, it is a good idea to look for
66+
alternative approaches. They can make your code cleaner and less error-prone.
67+
68+
In this case, there is a better alternative: the `Vec::drain` method. It accepts
69+
a range parameter to specify which items to remove from the `Vec`, and returns
70+
an iterator of those items. Passing the `..` range syntax will remove *every*
71+
value from the `Vec`.
72+
73+
So we need to update the `ThreadPool` `drop` implementation like this:
6274

6375
<Listing file-name="src/lib.rs">
6476

6577
```rust,ignore,does_not_compile
66-
{{#rustdoc_include ../listings/ch21-web-server/no-listing-04-update-worker-definition/src/lib.rs:here}}
78+
{{#rustdoc_include ../listings/ch21-web-server/no-listing-04-update-drop-definition/src/lib.rs:here}}
6779
```
6880

6981
</Listing>
7082

71-
Now let’s lean on the compiler to find the other places that need to change.
72-
Checking this code, we get two errors:
73-
74-
```console
75-
{{#include ../listings/ch21-web-server/no-listing-04-update-worker-definition/output.txt}}
76-
```
77-
78-
Let’s address the second error, which points to the code at the end of
79-
`Worker::new`; we need to wrap the `thread` value in `Some` when we create a
80-
new `Worker`. Make the following changes to fix this error:
81-
82-
<Listing file-name="src/lib.rs">
83-
84-
```rust,ignore,does_not_compile
85-
{{#rustdoc_include ../listings/ch21-web-server/no-listing-05-fix-worker-new/src/lib.rs:here}}
86-
```
87-
88-
</Listing>
89-
90-
The first error is in our `Drop` implementation. We mentioned earlier that we
91-
intended to call `take` on the `Option` value to move `thread` out of `worker`.
92-
The following changes will do so:
93-
94-
<Listing file-name="src/lib.rs">
95-
96-
```rust,ignore,not_desired_behavior
97-
{{#rustdoc_include ../listings/ch21-web-server/no-listing-06-fix-threadpool-drop/src/lib.rs:here}}
98-
```
99-
100-
</Listing>
101-
102-
As discussed in Chapter 18, the `take` method on `Option` takes the `Some`
103-
variant out and leaves `None` in its place. We’re using `if let` to destructure
104-
the `Some` and get the thread; then we call `join` on the thread. If a worker’s
105-
thread is already `None`, we know that worker has already had its thread
106-
cleaned up, so nothing happens in that case.
83+
This resolves the compiler error and does not require any other changes to our
84+
code.
10785

10886
### Signaling to the Threads to Stop Listening for Jobs
10987

@@ -120,9 +98,9 @@ implementation and then a change in the `Worker` loop.
12098

12199
First, we’ll change the `ThreadPool` `drop` implementation to explicitly drop
122100
the `sender` before waiting for the threads to finish. Listing 21-23 shows the
123-
changes to `ThreadPool` to explicitly drop `sender`. We use the same `Option`
124-
and `take` technique as we did with the thread to be able to move `sender` out
125-
of `ThreadPool`:
101+
changes to `ThreadPool` to explicitly drop `sender`. Unlike with the `workers`,
102+
here we *do* need to use an `Option` to be able to move `sender` out of
103+
`ThreadPool` with `Option::take`.
126104

127105
<Listing number="21-23" file-name="src/lib.rs" caption="Explicitly drop `sender` before joining the worker threads">
128106

0 commit comments

Comments
 (0)