Remove FnBox and use builtin impl FnOnce for Box<FnOnce()> instead.

This commit is contained in:
CrLF0710 2019-04-09 17:05:20 +08:00
parent b93ec30bbc
commit 37a17ef40c
3 changed files with 8 additions and 122 deletions

View File

@ -160,7 +160,6 @@ Filesystem
filesystem's
filesystems
Firefox
FnBox
FnMut
FnOnce
formatter

View File

@ -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<T>` ensures that only one
`Worker` thread at a time is trying to request a job.
Theoretically, this code should compile. Unfortunately, the Rust compiler isnt
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<T>` (which is what our `Job` type
alias is), the closure needs to move itself *out* of the `Box<T>` because the
closure takes ownership of `self` when we call it. In general, Rust doesnt
allow us to move a value out of a `Box<T>` because Rust doesnt know how big
the value inside the `Box<T>` will be: recall in Chapter 15 that we used
`Box<T>` precisely because we had something of an unknown size that we wanted
to store in a `Box<T>` 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<Self>`, which allows the method to take ownership of a `Self` value stored
in a `Box<T>`. Thats exactly what we want to do here, but unfortunately Rust
wont let us: the part of Rust that implements behavior when a closure is
called isnt implemented using `self: Box<Self>`. So Rust doesnt yet
understand that it could use `self: Box<Self>` in this situation to take
ownership of the closure and move the closure out of the `Box<T>`.
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 youve
finished this book, we would love for you to join in.
But for now, lets 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<T>` using `self: Box<Self>`; 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<Self>` 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.
<span class="filename">Filename: src/lib.rs</span>
```rust,ignore
trait FnBox {
fn call_box(self: Box<Self>);
}
impl<F: FnOnce()> FnBox for F {
fn call_box(self: Box<F>) {
(*self)()
}
}
type Job = Box<dyn FnBox + Send + 'static>;
// --snip--
impl Worker {
fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> 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,
}
}
}
```
<span class="caption">Listing 20-21: Adding a new trait `FnBox` to work around
the current limitations of `Box<FnOnce()>`</span>
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<Self>` to take ownership of `self` and move the
value out of the `Box<T>`.
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<T>` 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 dont have to change anything
about the actual values were 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. Dont worry if it doesnt 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 didnt write the worker thread code as shown in Listing 20-22.
why we didnt write the worker thread code as shown in Listing 20-21.
<span class="filename">Filename: src/lib.rs</span>
@ -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 {
}
```
<span class="caption">Listing 20-22: An alternative implementation of
<span class="caption">Listing 20-21: An alternative implementation of
`Worker::new` using `while let`</span>
This code compiles and runs but doesnt 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 dont think carefully about the lifetime of the
`MutexGuard<T>`. 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

View File

@ -450,17 +450,7 @@ pub struct ThreadPool {
sender: mpsc::Sender<Message>,
}
trait FnBox {
fn call_box(self: Box<Self>);
}
impl<F: FnOnce()> FnBox for F {
fn call_box(self: Box<F>) {
(*self)()
}
}
type Job = Box<dyn FnBox + Send + 'static>;
type Job = Box<dyn FnOnce() + Send + 'static>;
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);