Skip to content

Commit ab04ad6

Browse files
committed
Incorporate libs-api team feedback.
1 parent c38bca6 commit ab04ad6

File tree

1 file changed

+13
-42
lines changed

1 file changed

+13
-42
lines changed

text/0000-scoped-threads.md

Lines changed: 13 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@ let var = String::from("foo");
1717
thread::scope(|s| {
1818
s.spawn(|_| println!("borrowed from thread #1: {}", var));
1919
s.spawn(|_| println!("borrowed from thread #2: {}", var));
20-
})
21-
.unwrap();
20+
});
2221
```
2322

2423
# Motivation
@@ -81,7 +80,7 @@ handle1.join().unwrap();
8180
handle2.join().unwrap();
8281
```
8382

84-
Scoped threads coming to the rescue! By opening a new `thread::scope()` block,
83+
Scoped threads to the rescue! By opening a new `thread::scope()` block,
8584
we can prove to the compiler that all threads spawned within this scope will
8685
also die inside the scope:
8786

@@ -99,8 +98,7 @@ thread::scope(|s| {
9998

10099
handle1.join().unwrap();
101100
handle2.join().unwrap();
102-
})
103-
.unwrap();
101+
});
104102
```
105103

106104
That means variables living outside the scope can be borrowed without any
@@ -120,13 +118,11 @@ thread::scope(|s| {
120118
s.spawn(|_| {
121119
println!("thread #2 says: {}", greeting);
122120
});
123-
})
124-
.unwrap();
121+
});
125122
```
126123

127-
Note that `thread::scope()` returns a `Result` that will be `Ok` if all
128-
automatically joined threads have successfully completed, i.e. they haven't
129-
panicked.
124+
When taking advantage of automatic joining in this way, note that `thread::scope()`
125+
will panic if any of the automatically joined threads has panicked.
130126

131127
You might've noticed that scoped threads now take a single argument, which is
132128
just another reference to `s`. Since `s` lives inside the scope, we cannot borrow
@@ -139,8 +135,7 @@ thread::scope(|s| {
139135
println!("I belong to the same `thread::scope()` as my parent thread")
140136
});
141137
});
142-
})
143-
.unwrap();
138+
});
144139
```
145140

146141
# Reference-level explanation
@@ -165,7 +160,7 @@ inside the scope. The lifetime relations are:
165160
Next, we need the `scope()` and `spawn()` functions:
166161

167162
```rust
168-
fn scope<'env, F, T>(f: F) -> Result<T>
163+
fn scope<'env, F, T>(f: F) -> T
169164
where
170165
F: FnOnce(&Scope<'env>) -> T;
171166

@@ -202,28 +197,6 @@ impl Builder {
202197
}
203198
```
204199

205-
It's also worth pointing out what exactly happens at the scope end when all
206-
unjoined threads get automatically joined. If all joins succeed, we take
207-
the result of the main closure passed to `scope()` and wrap it inside `Ok`.
208-
209-
If any thread panics (and in fact multiple threads can panic), we collect
210-
all those panics into a `Vec`, box it, and finally wrap it inside `Err`.
211-
The error type is then erased because `thread::Result<T>` is just an
212-
alias for:
213-
214-
```rust
215-
Result<T, Box<dyn Any + Send + 'static>>
216-
```
217-
218-
This way we can do `thread::scope(...).unwrap()` to propagate all panics
219-
in child threads into the main parent thread.
220-
221-
If the main `scope()` closure has panicked after spawning threads, we
222-
just resume unwinding after joining child threads.
223-
224-
Crossbeam's logic for error handling can be found
225-
[here](https://github.com/crossbeam-rs/crossbeam/blob/79210d6ae34a3e84b23546d8abc5c4b81b206019/crossbeam-utils/src/thread.rs#L167-L193).
226-
227200
# Drawbacks
228201
[drawbacks]: #drawbacks
229202

@@ -276,9 +249,9 @@ Rust 1.0 era. The new one is from the last year's big revamp:
276249

277250
There are several differences between old and new scoped threads:
278251

279-
1. `scope()` now returns a `thread::Result<T>` rather than `T`. This is because
280-
panics in the old design were just silently ignored, which is not good.
281-
By returning a `Result`, the user can handle panics in whatever way they want.
252+
1. `scope()` now propagates unhandled panics from child threads.
253+
In the old design, panics were silently ignored.
254+
Users can still handle panics by manually working with `ScopedJoinHandle`s.
282255

283256
2. The closure passed to `Scope::spawn()` now takes a `&Scope<'env>` argument that
284257
allows one to spawn nested threads, which was not possible with the old design.
@@ -292,14 +265,12 @@ There are several differences between old and new scoped threads:
292265

293266
Rayon also has [scopes](https://docs.rs/rayon/1.0.3/rayon/struct.Scope.html),
294267
but they work on a different abstraction level - Rayon spawns tasks rather than
295-
threads. Its API is almost the same as proposed in this RFC, the only
296-
difference being that `scope()` propagates panics instead of returning `Result`.
297-
This behavior makes more sense for tasks than threads.
268+
threads. Its API is the same as the one proposed in this RFC.
298269

299270
# Unresolved questions
300271
[unresolved-questions]: #unresolved-questions
301272

302-
None.
273+
Can this concept be extended to async? Would there be any behavioral or API differences?
303274

304275
# Future possibilities
305276
[future-possibilities]: #future-possibilities

0 commit comments

Comments
 (0)