Skip to content

Commit c38bca6

Browse files
author
Stjepan Glavina
committed
Bring ScopedJoinHandle back
1 parent d53412a commit c38bca6

File tree

1 file changed

+41
-28
lines changed

1 file changed

+41
-28
lines changed

text/0000-scoped-threads.md

Lines changed: 41 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ Scoped threads in [Crossbeam](https://docs.rs/crossbeam/0.7.1/crossbeam/thread/i
4141
have matured through years of experience and today we have a design that feels solid
4242
enough to be promoted into the standard library.
4343

44-
See the [rationale-and-alternatives](#rationale-and-alternatives) section for more.
44+
See the [Rationale and alternatives](#rationale-and-alternatives) section for more.
4545

4646
# Guide-level explanation
4747
[guide-level-explanation]: #guide-level-explanation
@@ -146,46 +146,63 @@ thread::scope(|s| {
146146
# Reference-level explanation
147147
[reference-level-explanation]: #reference-level-explanation
148148

149-
We add a single new type to the `std::thread` module:
149+
We add two new types to the `std::thread` module:
150150

151151
```rust
152-
struct Scope<'a> {}
152+
struct Scope<'env> {}
153+
struct ScopedJoinHandle<'scope, T> {}
154+
```
155+
156+
Lifetime `'env` represents the environment outside the scope, while
157+
`'scope` represents the scope itself. More precisely, everything
158+
outside the scope outlives `'env` and `'scope` outlives everything
159+
inside the scope. The lifetime relations are:
160+
161+
```
162+
'variables_outside: 'env: 'scope: 'variables_inside
153163
```
154164

155165
Next, we need the `scope()` and `spawn()` functions:
156166

157167
```rust
158-
fn scope<'a, F, T>(f: F) -> Result<T>
168+
fn scope<'env, F, T>(f: F) -> Result<T>
159169
where
160-
F: FnOnce(&Scope<'a>) -> T;
170+
F: FnOnce(&Scope<'env>) -> T;
161171

162-
impl<'a> Scope<'a> {
163-
fn spawn<F, T>(&self, f: F) -> JoinHandle<T>
172+
impl<'env> Scope<'env> {
173+
fn spawn<'scope, F, T>(&'scope self, f: F) -> ScopedJoinHandle<'scope, T>
164174
where
165-
F: FnOnce(&Scope<'a>) -> T + Send + 'a,
166-
T: Send + 'a;
175+
F: FnOnce(&Scope<'env>) -> T + Send + 'env,
176+
T: Send + 'env;
167177
}
168178
```
169179

170-
There's just one more thing that will make the API complete: The thread builder
171-
needs to be able to spawn threads inside a scope.
180+
That's the gist of scoped threads, really.
181+
182+
Now we just need two more things to make the API complete. First, `ScopedJoinHandle`
183+
is equivalent to `JoinHandle` but tied to the `'scope` lifetime, so it will have
184+
the same methods. Second, the thread builder needs to be able to spawn threads
185+
inside a scope:
172186

173187
```rust
188+
impl<'scope, T> ScopedJoinHandle<'scope, T> {
189+
fn join(self) -> Result<T>;
190+
fn thread(&self) -> &Thread;
191+
}
192+
174193
impl Builder {
175-
fn spawn_scoped<'a, F, T>(self, scope: &Scope<'a>, f: F) -> io::Result<JoinHandle<T>>
194+
fn spawn_scoped<'scope, 'env, F, T>(
195+
self,
196+
&'scope Scope<'env>,
197+
f: F,
198+
) -> io::Result<ScopedJoinHandle<'scope, T>>
176199
where
177-
F: FnOnce(&Scope<'a>) -> T + Send + 'a,
178-
T: Send + 'a;
200+
F: FnOnce(&Scope<'env>) -> T + Send + 'env,
201+
T: Send + 'env;
179202
}
180203
```
181204

182-
Note that this interface is a bit simpler than the one in Crossbeam
183-
because we can now merge `JoinHandle` and `ScopedJoinHandle` into a single type.
184-
Moreover, in Crossbeam, `ScopedJoinHandle` is generic over `'scope`, which is
185-
not really necessary for soundness so we can remove that lifetime to simplify
186-
things further.
187-
188-
It's also worth discussing what exactly happens at the scope end when all
205+
It's also worth pointing out what exactly happens at the scope end when all
189206
unjoined threads get automatically joined. If all joins succeed, we take
190207
the result of the main closure passed to `scope()` and wrap it inside `Ok`.
191208

@@ -229,10 +246,7 @@ several advantages to having them in the standard library:
229246
feels like a missing piece in the standard library.
230247

231248
* Implementing scoped threads is very tricky to get right so it's good to have a
232-
reliable solution provided by the standard library. Also, scoped threads in `libstd`
233-
will be simpler because we don't need to introduce a special type for
234-
[scoped join handles](https://docs.rs/crossbeam/0.7.1/crossbeam/thread/struct.ScopedJoinHandle.html)
235-
or [builders](https://docs.rs/crossbeam/0.7.1/crossbeam/thread/struct.ScopedThreadBuilder.html).
249+
reliable solution provided by the standard library.
236250

237251
* There are many examples in the official documentation and books that could be
238252
simplified by scoped threads.
@@ -242,7 +256,7 @@ several advantages to having them in the standard library:
242256
This is sometimes a problem in unit tests, where "dangling" threads can accumulate
243257
if unit tests spawn threads and forget to join them.
244258

245-
* It's indisputable that users keep asking for scoped threads on IRC and forums
259+
* Users keep asking for scoped threads on IRC and forums
246260
all the time. Having them as a "blessed" pattern in `std::thread` would be beneficial
247261
to everyone.
248262

@@ -274,8 +288,7 @@ There are several differences between old and new scoped threads:
274288
non-obvious behavior.
275289

276290
4. `ScopedJoinHandle` got parametrized over `'scope` in order to prevent it from
277-
escaping the scope. However, it turns out this is not really necessary for
278-
soundness and was just a conservative safeguard.
291+
escaping the scope.
279292

280293
Rayon also has [scopes](https://docs.rs/rayon/1.0.3/rayon/struct.Scope.html),
281294
but they work on a different abstraction level - Rayon spawns tasks rather than

0 commit comments

Comments
 (0)