Skip to content

Commit

Permalink
Remove FnBox and use builtin impl FnOnce for Box<FnOnce()> instead.
Browse files Browse the repository at this point in the history
  • Loading branch information
crlf0710 committed Apr 10, 2019
1 parent b93ec30 commit 37a17ef
Show file tree
Hide file tree
Showing 3 changed files with 8 additions and 122 deletions.
1 change: 0 additions & 1 deletion ci/dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,6 @@ Filesystem
filesystem's
filesystems
Firefox
FnBox
FnMut
FnOnce
formatter
Expand Down
115 changes: 6 additions & 109 deletions src/ch20-02-multithreaded.md
Original file line number Diff line number Diff line change
Expand Up @@ -944,7 +944,7 @@ impl Worker {
println!("Worker {} got a job; executing.", id);
(*job)();
job();
}
});
Expand Down Expand Up @@ -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 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<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 doesn’t
allow us to move a value out of a `Box<T>` because Rust doesn’t 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>`. 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<Self>`. So Rust doesn’t 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 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<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 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:
Expand Down Expand Up @@ -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.

<span class="filename">Filename: src/lib.rs</span>

Expand All @@ -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();
}
});
Expand All @@ -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 doesn’t result in the desired threading
Expand All @@ -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<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
Expand Down
14 changes: 2 additions & 12 deletions src/ch20-03-graceful-shutdown-and-cleanup.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 37a17ef

Please sign in to comment.