From 37a17ef40ccee85b28963cfe8d83ff8d66bd7717 Mon Sep 17 00:00:00 2001 From: CrLF0710 Date: Tue, 9 Apr 2019 17:05:20 +0800 Subject: [PATCH] Remove FnBox and use builtin impl FnOnce for Box instead. --- ci/dictionary.txt | 1 - src/ch20-02-multithreaded.md | 115 +------------------ src/ch20-03-graceful-shutdown-and-cleanup.md | 14 +-- 3 files changed, 8 insertions(+), 122 deletions(-) diff --git a/ci/dictionary.txt b/ci/dictionary.txt index 3a780fd7ad..a910123062 100644 --- a/ci/dictionary.txt +++ b/ci/dictionary.txt @@ -160,7 +160,6 @@ Filesystem filesystem's filesystems Firefox -FnBox FnMut FnOnce formatter diff --git a/src/ch20-02-multithreaded.md b/src/ch20-02-multithreaded.md index 2e64463486..33e74f0dc7 100644 --- a/src/ch20-02-multithreaded.md +++ b/src/ch20-02-multithreaded.md @@ -944,7 +944,7 @@ impl Worker { println!("Worker {} got a job; executing.", id); - (*job)(); + job(); } }); @@ -976,109 +976,6 @@ The call to `recv` blocks, so if there is no job yet, the current thread will wait until a job becomes available. The `Mutex` ensures that only one `Worker` thread at a time is trying to request a job. -Theoretically, this code should compile. Unfortunately, the Rust compiler isn’t -perfect yet, and we get this error: - -```text -error[E0161]: cannot move a value of type std::ops::FnOnce() + -std::marker::Send: the size of std::ops::FnOnce() + std::marker::Send cannot be -statically determined - --> src/lib.rs:63:17 - | -63 | (*job)(); - | ^^^^^^ -``` - -This error is fairly cryptic because the problem is fairly cryptic. To call a -`FnOnce` closure that is stored in a `Box` (which is what our `Job` type -alias is), the closure needs to move itself *out* of the `Box` because the -closure takes ownership of `self` when we call it. In general, Rust doesn’t -allow us to move a value out of a `Box` because Rust doesn’t know how big -the value inside the `Box` will be: recall in Chapter 15 that we used -`Box` precisely because we had something of an unknown size that we wanted -to store in a `Box` to get a value of a known size. - -As you saw in Listing 17-15, we can write methods that use the syntax `self: -Box`, which allows the method to take ownership of a `Self` value stored -in a `Box`. That’s exactly what we want to do here, but unfortunately Rust -won’t let us: the part of Rust that implements behavior when a closure is -called isn’t implemented using `self: Box`. So Rust doesn’t yet -understand that it could use `self: Box` in this situation to take -ownership of the closure and move the closure out of the `Box`. - -Rust is still a work in progress with places where the compiler could be -improved, but in the future, the code in Listing 20-20 should work just fine. -People just like you are working to fix this and other issues! After you’ve -finished this book, we would love for you to join in. - -But for now, let’s work around this problem using a handy trick. We can tell -Rust explicitly that in this case we can take ownership of the value inside the -`Box` using `self: Box`; then, once we have ownership of the closure, -we can call it. This involves defining a new trait `FnBox` with the method -`call_box` that will use `self: Box` in its signature, defining `FnBox` -for any type that implements `FnOnce()`, changing our type alias to use the new -trait, and changing `Worker` to use the `call_box` method. These changes are -shown in Listing 20-21. - -Filename: src/lib.rs - -```rust,ignore -trait FnBox { - fn call_box(self: Box); -} - -impl FnBox for F { - fn call_box(self: Box) { - (*self)() - } -} - -type Job = Box; - -// --snip-- - -impl Worker { - fn new(id: usize, receiver: Arc>>) -> Worker { - let thread = thread::spawn(move || { - loop { - let job = receiver.lock().unwrap().recv().unwrap(); - - println!("Worker {} got a job; executing.", id); - - job.call_box(); - } - }); - - Worker { - id, - thread, - } - } -} -``` - -Listing 20-21: Adding a new trait `FnBox` to work around -the current limitations of `Box` - -First, we create a new trait named `FnBox`. This trait has the one method -`call_box`, which is similar to the `call` methods on the other `Fn*` traits -except that it takes `self: Box` to take ownership of `self` and move the -value out of the `Box`. - -Next, we implement the `FnBox` trait for any type `F` that implements the -`FnOnce()` trait. Effectively, this means that any `FnOnce()` closures can use -our `call_box` method. The implementation of `call_box` uses `(*self)()` to -move the closure out of the `Box` and call the closure. - -We now need our `Job` type alias to be a `Box` of anything that implements our -new trait `FnBox`. This will allow us to use `call_box` in `Worker` when we get -a `Job` value instead of invoking the closure directly. Implementing the -`FnBox` trait for any `FnOnce()` closure means we don’t have to change anything -about the actual values we’re sending down the channel. Now Rust is able to -recognize that what we want to do is fine. - -This trick is very sneaky and complicated. Don’t worry if it doesn’t make -perfect sense; someday, it will be completely unnecessary. With the implementation of this trick, our thread pool is in a working state! Give it a `cargo run` and make some requests: @@ -1136,7 +1033,7 @@ thread run them. > limitation is not caused by our web server. After learning about the `while let` loop in Chapter 18, you might be wondering -why we didn’t write the worker thread code as shown in Listing 20-22. +why we didn’t write the worker thread code as shown in Listing 20-21. Filename: src/lib.rs @@ -1149,7 +1046,7 @@ impl Worker { while let Ok(job) = receiver.lock().unwrap().recv() { println!("Worker {} got a job; executing.", id); - job.call_box(); + job(); } }); @@ -1161,7 +1058,7 @@ impl Worker { } ``` -Listing 20-22: An alternative implementation of +Listing 20-21: An alternative implementation of `Worker::new` using `while let` This code compiles and runs but doesn’t result in the desired threading @@ -1175,13 +1072,13 @@ lock. But this implementation can also result in the lock being held longer than intended if we don’t think carefully about the lifetime of the `MutexGuard`. Because the values in the `while` expression remain in scope for the duration of the block, the lock remains held for the duration of the -call to `job.call_box()`, meaning other workers cannot receive jobs. +call to `job()`, meaning other workers cannot receive jobs. By using `loop` instead and acquiring the lock and a job within the block rather than outside it, the `MutexGuard` returned from the `lock` method is dropped as soon as the `let job` statement ends. This ensures that the lock is held during the call to `recv`, but it is released before the call to -`job.call_box()`, allowing multiple requests to be serviced concurrently. +`job()`, allowing multiple requests to be serviced concurrently. [creating-type-synonyms-with-type-aliases]: ch19-04-advanced-types.html#creating-type-synonyms-with-type-aliases diff --git a/src/ch20-03-graceful-shutdown-and-cleanup.md b/src/ch20-03-graceful-shutdown-and-cleanup.md index f36fa026ee..47f4ef0f06 100644 --- a/src/ch20-03-graceful-shutdown-and-cleanup.md +++ b/src/ch20-03-graceful-shutdown-and-cleanup.md @@ -450,17 +450,7 @@ pub struct ThreadPool { sender: mpsc::Sender, } -trait FnBox { - fn call_box(self: Box); -} - -impl FnBox for F { - fn call_box(self: Box) { - (*self)() - } -} - -type Job = Box; +type Job = Box; impl ThreadPool { /// Create a new ThreadPool. @@ -536,7 +526,7 @@ impl Worker { Message::NewJob(job) => { println!("Worker {} got a job; executing.", id); - job.call_box(); + job(); }, Message::Terminate => { println!("Worker {} was told to terminate.", id);