]> git.proxmox.com Git - rustc.git/blob - src/doc/book/second-edition/src/ch20-03-designing-the-interface.md
New upstream version 1.25.0+dfsg1
[rustc.git] / src / doc / book / second-edition / src / ch20-03-designing-the-interface.md
1 ## Designing the Thread Pool Interface
2
3 Let’s talk about what using the pool should look like. The authors often find
4 that when trying to design some code, writing the client interface first can
5 really help guide your design. Write the API of the code to be structured in
6 the way you’d want to call it, then implement the functionality within that
7 structure rather than implementing the functionality then designing the public
8 API.
9
10 Similar to how we used Test Driven Development in the project in Chapter 12,
11 we’re going to use Compiler Driven Development here. We’re going to write the
12 code that calls the functions we wish we had, then we’ll lean on the compiler
13 to tell us what we should change next. The compiler error messages will guide
14 our implementation.
15
16 ### Code Structure if We Could Use `thread::spawn`
17
18 First, let’s explore what the code to create a new thread for every connection
19 could look like. This isn’t our final plan due to the problems with potentially
20 spawning an unlimited number of threads that we talked about earlier, but it’s
21 a start. Listing 20-11 shows the changes to `main` to spawn a new thread to
22 handle each stream within the `for` loop:
23
24 <span class="filename">Filename: src/main.rs</span>
25
26 ```rust,no_run
27 # use std::thread;
28 # use std::io::prelude::*;
29 # use std::net::TcpListener;
30 # use std::net::TcpStream;
31 #
32 fn main() {
33 let listener = TcpListener::bind("127.0.0.1:8080").unwrap();
34
35 for stream in listener.incoming() {
36 let stream = stream.unwrap();
37
38 thread::spawn(|| {
39 handle_connection(stream);
40 });
41 }
42 }
43 # fn handle_connection(mut stream: TcpStream) {}
44 ```
45
46 <span class="caption">Listing 20-11: Spawning a new thread for each
47 stream</span>
48
49 As we learned in Chapter 16, `thread::spawn` will create a new thread and then
50 run the code in the closure in it. If you run this code and load `/sleep` and
51 then `/` in two browser tabs, you’ll indeed see the request to `/` doesn’t have
52 to wait for `/sleep` to finish. But as we mentioned, this will eventually
53 overwhelm the system since we’re making new threads without any limit.
54
55 ### Creating a Similar Interface for `ThreadPool`
56
57 We want our thread pool to work in a similar, familiar way so that switching
58 from threads to a thread pool doesn’t require large changes to the code we want
59 to run in the pool. Listing 20-12 shows the hypothetical interface for a
60 `ThreadPool` struct we’d like to use instead of `thread::spawn`:
61
62 <span class="filename">Filename: src/main.rs</span>
63
64 ```rust,no_run
65 # use std::thread;
66 # use std::io::prelude::*;
67 # use std::net::TcpListener;
68 # use std::net::TcpStream;
69 # struct ThreadPool;
70 # impl ThreadPool {
71 # fn new(size: u32) -> ThreadPool { ThreadPool }
72 # fn execute<F>(&self, f: F)
73 # where F: FnOnce() + Send + 'static {}
74 # }
75 #
76 fn main() {
77 let listener = TcpListener::bind("127.0.0.1:8080").unwrap();
78 let pool = ThreadPool::new(4);
79
80 for stream in listener.incoming() {
81 let stream = stream.unwrap();
82
83 pool.execute(|| {
84 handle_connection(stream);
85 });
86 }
87 }
88 # fn handle_connection(mut stream: TcpStream) {}
89 ```
90
91 <span class="caption">Listing 20-12: How we want to be able to use the
92 `ThreadPool` we’re going to implement</span>
93
94 We use `ThreadPool::new` to create a new thread pool with a configurable number
95 of threads, in this case four. Then, in the `for` loop, `pool.execute` will
96 work in a similar way to `thread::spawn`.
97
98 ### Compiler Driven Development to Get the API Compiling
99
100 Go ahead and make the changes in Listing 20-12 to *src/main.rs*, and let’s use
101 the compiler errors to drive our development. Here’s the first error we get:
102
103 ```text
104 $ cargo check
105 Compiling hello v0.1.0 (file:///projects/hello)
106 error[E0433]: failed to resolve. Use of undeclared type or module `ThreadPool`
107 --> src\main.rs:10:16
108 |
109 10 | let pool = ThreadPool::new(4);
110 | ^^^^^^^^^^^^^^^ Use of undeclared type or module
111 `ThreadPool`
112
113 error: aborting due to previous error
114 ```
115
116 Great, we need a `ThreadPool`. Let’s switch the `hello` crate from a binary
117 crate to a library crate to hold our `ThreadPool` implementation, since the
118 thread pool implementation will be independent of the particular kind of work
119 that we’re doing in our web server. Once we’ve got the thread pool library
120 written, we could use that functionality to do whatever work we want to do, not
121 just serve web requests.
122
123 So create *src/lib.rs* that contains the simplest definition of a `ThreadPool`
124 struct that we can have for now:
125
126 <span class="filename">Filename: src/lib.rs</span>
127
128 ```rust
129 pub struct ThreadPool;
130 ```
131
132 Then create a new directory, *src/bin*, and move the binary crate rooted in
133 *src/main.rs* into *src/bin/main.rs*. This will make the library crate be the
134 primary crate in the *hello* directory; we can still run the binary in
135 *src/bin/main.rs* using `cargo run` though. After moving the *main.rs* file,
136 edit it to bring the library crate in and bring `ThreadPool` into scope by
137 adding this at the top of *src/bin/main.rs*:
138
139 <span class="filename">Filename: src/bin/main.rs</span>
140
141 ```rust,ignore
142 extern crate hello;
143 use hello::ThreadPool;
144 ```
145
146 And try again in order to get the next error that we need to address:
147
148 ```text
149 $ cargo check
150 Compiling hello v0.1.0 (file:///projects/hello)
151 error: no associated item named `new` found for type `hello::ThreadPool` in the
152 current scope
153 --> src\main.rs:13:16
154 |
155 13 | let pool = ThreadPool::new(4);
156 | ^^^^^^^^^^^^^^^
157 |
158 ```
159
160 Cool, the next thing is to create an associated function named `new` for
161 `ThreadPool`. We also know that `new` needs to have one parameter that can
162 accept `4` as an argument, and `new` should return a `ThreadPool` instance.
163 Let’s implement the simplest `new` function that will have those
164 characteristics:
165
166 <span class="filename">Filename: src/lib.rs</span>
167
168 ```rust
169 pub struct ThreadPool;
170
171 impl ThreadPool {
172 pub fn new(size: u32) -> ThreadPool {
173 ThreadPool
174 }
175 }
176 ```
177
178 We picked `u32` as the type of the `size` parameter, since we know that a
179 negative number of threads makes no sense. `u32` is a solid default. Once we
180 actually implement `new` for real, we’ll reconsider whether this is the right
181 choice for what the implementation needs, but for now, we’re just working
182 through compiler errors.
183
184 Let’s check the code again:
185
186 ```text
187 $ cargo check
188 Compiling hello v0.1.0 (file:///projects/hello)
189 warning: unused variable: `size`, #[warn(unused_variables)] on by default
190 --> src/lib.rs:4:16
191 |
192 4 | pub fn new(size: u32) -> ThreadPool {
193 | ^^^^
194
195 error: no method named `execute` found for type `hello::ThreadPool` in the
196 current scope
197 --> src/main.rs:18:14
198 |
199 18 | pool.execute(|| {
200 | ^^^^^^^
201 ```
202
203 Okay, a warning and an error. Ignoring the warning for a moment, the error is
204 because we don’t have an `execute` method on `ThreadPool`. Let’s define one,
205 and we need it to take a closure. If you remember from Chapter 13, we can take
206 closures as arguments with three different traits: `Fn`, `FnMut`, and `FnOnce`.
207 What kind of closure should we use? Well, we know we’re going to end up doing
208 something similar to `thread::spawn`; what bounds does the signature of
209 `thread::spawn` have on its argument? Let’s look at the documentation, which
210 says:
211
212 ```rust,ignore
213 pub fn spawn<F, T>(f: F) -> JoinHandle<T>
214 where
215 F: FnOnce() -> T + Send + 'static,
216 T: Send + 'static
217 ```
218
219 `F` is the parameter we care about here; `T` is related to the return value and
220 we’re not concerned with that. Given that `spawn` uses `FnOnce` as the trait
221 bound on `F`, it’s probably what we want as well, since we’ll eventually be
222 passing the argument we get in `execute` to `spawn`. We can be further
223 confident that `FnOnce` is the trait that we want to use since the thread for
224 running a request is only going to execute that request’s closure one time.
225
226 `F` also has the trait bound `Send` and the lifetime bound `'static`, which
227 also make sense for our situation: we need `Send` to transfer the closure from
228 one thread to another, and `'static` because we don’t know how long the thread
229 will execute. Let’s create an `execute` method on `ThreadPool` that will take a
230 generic parameter `F` with these bounds:
231
232 <span class="filename">Filename: src/lib.rs</span>
233
234 ```rust
235 # pub struct ThreadPool;
236 impl ThreadPool {
237 // --snip--
238
239 pub fn execute<F>(&self, f: F)
240 where
241 F: FnOnce() + Send + 'static
242 {
243
244 }
245 }
246 ```
247
248 The `FnOnce` trait still needs the `()` after it since this `FnOnce` is
249 representing a closure that takes no parameters and doesn’t return a value.
250 Just like function definitions, the return type can be omitted from the
251 signature, but even if we have no parameters, we still need the parentheses.
252
253 Again, since we’re working on getting the interface compiling, we’re adding the
254 simplest implementation of the `execute` method, which does nothing. Let’s
255 check again:
256
257 ```text
258 $ cargo check
259 Compiling hello v0.1.0 (file:///projects/hello)
260 warning: unused variable: `size`, #[warn(unused_variables)] on by default
261 --> src/lib.rs:4:16
262 |
263 4 | pub fn new(size: u32) -> ThreadPool {
264 | ^^^^
265
266 warning: unused variable: `f`, #[warn(unused_variables)] on by default
267 --> src/lib.rs:8:30
268 |
269 8 | pub fn execute<F>(&self, f: F)
270 | ^
271 ```
272
273 Only warnings now! It compiles! Note that if you try `cargo run` and making a
274 request in the browser, though, you’ll see the errors in the browser again that
275 we saw in the beginning of the chapter. Our library isn’t actually calling the
276 closure passed to `execute` yet!
277
278 > A saying you might hear about languages with strict compilers like Haskell
279 > and Rust is “if the code compiles, it works.” This is a good time to remember
280 > that this is just a phrase and a feeling people sometimes have, it’s not
281 > actually universally true. Our project compiles, but it does absolutely
282 > nothing! If we were building a real, complete project, this would be a great
283 > time to start writing unit tests to check that the code compiles *and* has
284 > the behavior we want.