Skip to content

Commit ccce014

Browse files
authored
Rollup merge of rust-lang#94559 - m-ou-se:thread-scope-spawn-closure-without-arg, r=Mark-Simulacrum
Remove argument from closure in thread::Scope::spawn. This implements ``@danielhenrymantilla's`` [suggestion](rust-lang#93203 (comment)) for improving the scoped threads interface. Summary: The `Scope` type gets an extra lifetime argument, which represents basically its own lifetime that will be used in `&'scope Scope<'scope, 'env>`: ```diff - pub struct Scope<'env> { .. }; + pub struct Scope<'scope, 'env: 'scope> { .. } pub fn scope<'env, F, T>(f: F) -> T where - F: FnOnce(&Scope<'env>) -> T; + F: for<'scope> FnOnce(&'scope Scope<'scope, 'env>) -> T; ``` This simplifies the `spawn` function, which now no longer passes an argument to the closure you give it, and now uses the `'scope` lifetime for everything: ```diff - pub fn spawn<'scope, F, T>(&'scope self, f: F) -> ScopedJoinHandle<'scope, T> + pub fn spawn<F, T>(&'scope self, f: F) -> ScopedJoinHandle<'scope, T> where - F: FnOnce(&Scope<'env>) -> T + Send + 'env, + F: FnOnce() -> T + Send + 'scope, - T: Send + 'env; + T: Send + 'scope; ``` The only difference the user will notice, is that their closure now takes no arguments anymore, even when spawning threads from spawned threads: ```diff thread::scope(|s| { - s.spawn(|_| { + s.spawn(|| { ... }); - s.spawn(|s| { + s.spawn(|| { ... - s.spawn(|_| ...); + s.spawn(|| ...); }); }); ``` <details><summary>And, as a bonus, errors get <em>slightly</em> better because now any lifetime issues point to the outermost <code>s</code> (since there is only one <code>s</code>), rather than the innermost <code>s</code>, making it clear that the lifetime lasts for the entire <code>thread::scope</code>. </summary> ```diff error[E0373]: closure may outlive the current function, but it borrows `a`, which is owned by the current function --> src/main.rs:9:21 | - 7 | s.spawn(|s| { - | - has type `&Scope<'1>` + 6 | thread::scope(|s| { + | - lifetime `'1` appears in the type of `s` 9 | s.spawn(|| println!("{:?}", a)); // might run after `a` is dropped | ^^ - `a` is borrowed here | | | may outlive borrowed value `a` | note: function requires argument type to outlive `'1` --> src/main.rs:9:13 | 9 | s.spawn(|| println!("{:?}", a)); // might run after `a` is dropped | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: to force the closure to take ownership of `a` (and any other referenced variables), use the `move` keyword | 9 | s.spawn(move || println!("{:?}", a)); // might run after `a` is dropped | ++++ " ``` </details> The downside is that the signature of `scope` and `Scope` gets slightly more complex, but in most cases the user wouldn't need to write those, as they just use the argument provided by `thread::scope` without having to name its type. Another downside is that this does not work nicely in Rust 2015 and Rust 2018, since in those editions, `s` would be captured by reference and not by copy. In those editions, the user would need to use `move ||` to capture `s` by copy. (Which is what the compiler suggests in the error.)
2 parents 5ce67da + a3d269e commit ccce014

File tree

2 files changed

+26
-24
lines changed

2 files changed

+26
-24
lines changed

library/core/src/sync/atomic.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ impl AtomicBool {
352352
/// let a = &*AtomicBool::from_mut_slice(&mut some_bools);
353353
/// std::thread::scope(|s| {
354354
/// for i in 0..a.len() {
355-
/// s.spawn(move |_| a[i].store(true, Ordering::Relaxed));
355+
/// s.spawn(move || a[i].store(true, Ordering::Relaxed));
356356
/// }
357357
/// });
358358
/// assert_eq!(some_bools, [true; 10]);
@@ -984,7 +984,7 @@ impl<T> AtomicPtr<T> {
984984
/// let a = &*AtomicPtr::from_mut_slice(&mut some_ptrs);
985985
/// std::thread::scope(|s| {
986986
/// for i in 0..a.len() {
987-
/// s.spawn(move |_| {
987+
/// s.spawn(move || {
988988
/// let name = Box::new(format!("thread{i}"));
989989
/// a[i].store(Box::into_raw(name), Ordering::Relaxed);
990990
/// });
@@ -1533,7 +1533,7 @@ macro_rules! atomic_int {
15331533
#[doc = concat!("let a = &*", stringify!($atomic_type), "::from_mut_slice(&mut some_ints);")]
15341534
/// std::thread::scope(|s| {
15351535
/// for i in 0..a.len() {
1536-
/// s.spawn(move |_| a[i].store(i as _, Ordering::Relaxed));
1536+
/// s.spawn(move || a[i].store(i as _, Ordering::Relaxed));
15371537
/// }
15381538
/// });
15391539
/// for (i, n) in some_ints.into_iter().enumerate() {

library/std/src/thread/scoped.rs

+23-21
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,24 @@ use crate::sync::Arc;
99
/// A scope to spawn scoped threads in.
1010
///
1111
/// See [`scope`] for details.
12-
pub struct Scope<'env> {
12+
pub struct Scope<'scope, 'env: 'scope> {
1313
data: ScopeData,
14-
/// Invariance over 'env, to make sure 'env cannot shrink,
14+
/// Invariance over 'scope, to make sure 'scope cannot shrink,
1515
/// which is necessary for soundness.
1616
///
1717
/// Without invariance, this would compile fine but be unsound:
1818
///
19-
/// ```compile_fail
19+
/// ```compile_fail,E0373
2020
/// #![feature(scoped_threads)]
2121
///
2222
/// std::thread::scope(|s| {
23-
/// s.spawn(|s| {
23+
/// s.spawn(|| {
2424
/// let a = String::from("abcd");
25-
/// s.spawn(|_| println!("{:?}", a)); // might run after `a` is dropped
25+
/// s.spawn(|| println!("{:?}", a)); // might run after `a` is dropped
2626
/// });
2727
/// });
2828
/// ```
29+
scope: PhantomData<&'scope mut &'scope ()>,
2930
env: PhantomData<&'env mut &'env ()>,
3031
}
3132

@@ -88,12 +89,12 @@ impl ScopeData {
8889
/// let mut x = 0;
8990
///
9091
/// thread::scope(|s| {
91-
/// s.spawn(|_| {
92+
/// s.spawn(|| {
9293
/// println!("hello from the first scoped thread");
9394
/// // We can borrow `a` here.
9495
/// dbg!(&a);
9596
/// });
96-
/// s.spawn(|_| {
97+
/// s.spawn(|| {
9798
/// println!("hello from the second scoped thread");
9899
/// // We can even mutably borrow `x` here,
99100
/// // because no other threads are using it.
@@ -109,7 +110,7 @@ impl ScopeData {
109110
#[track_caller]
110111
pub fn scope<'env, F, T>(f: F) -> T
111112
where
112-
F: FnOnce(&Scope<'env>) -> T,
113+
F: for<'scope> FnOnce(&'scope Scope<'scope, 'env>) -> T,
113114
{
114115
let scope = Scope {
115116
data: ScopeData {
@@ -118,6 +119,7 @@ where
118119
a_thread_panicked: AtomicBool::new(false),
119120
},
120121
env: PhantomData,
122+
scope: PhantomData,
121123
};
122124

123125
// Run `f`, but catch panics so we can make sure to wait for all the threads to join.
@@ -138,7 +140,7 @@ where
138140
}
139141
}
140142

141-
impl<'env> Scope<'env> {
143+
impl<'scope, 'env> Scope<'scope, 'env> {
142144
/// Spawns a new thread within a scope, returning a [`ScopedJoinHandle`] for it.
143145
///
144146
/// Unlike non-scoped threads, threads spawned with this function may
@@ -163,10 +165,10 @@ impl<'env> Scope<'env> {
163165
/// to recover from such errors.
164166
///
165167
/// [`join`]: ScopedJoinHandle::join
166-
pub fn spawn<'scope, F, T>(&'scope self, f: F) -> ScopedJoinHandle<'scope, T>
168+
pub fn spawn<F, T>(&'scope self, f: F) -> ScopedJoinHandle<'scope, T>
167169
where
168-
F: FnOnce(&Scope<'env>) -> T + Send + 'env,
169-
T: Send + 'env,
170+
F: FnOnce() -> T + Send + 'scope,
171+
T: Send + 'scope,
170172
{
171173
Builder::new().spawn_scoped(self, f).expect("failed to spawn thread")
172174
}
@@ -196,7 +198,7 @@ impl Builder {
196198
/// thread::scope(|s| {
197199
/// thread::Builder::new()
198200
/// .name("first".to_string())
199-
/// .spawn_scoped(s, |_|
201+
/// .spawn_scoped(s, ||
200202
/// {
201203
/// println!("hello from the {:?} scoped thread", thread::current().name());
202204
/// // We can borrow `a` here.
@@ -205,7 +207,7 @@ impl Builder {
205207
/// .unwrap();
206208
/// thread::Builder::new()
207209
/// .name("second".to_string())
208-
/// .spawn_scoped(s, |_|
210+
/// .spawn_scoped(s, ||
209211
/// {
210212
/// println!("hello from the {:?} scoped thread", thread::current().name());
211213
/// // We can even mutably borrow `x` here,
@@ -222,14 +224,14 @@ impl Builder {
222224
/// ```
223225
pub fn spawn_scoped<'scope, 'env, F, T>(
224226
self,
225-
scope: &'scope Scope<'env>,
227+
scope: &'scope Scope<'scope, 'env>,
226228
f: F,
227229
) -> io::Result<ScopedJoinHandle<'scope, T>>
228230
where
229-
F: FnOnce(&Scope<'env>) -> T + Send + 'env,
230-
T: Send + 'env,
231+
F: FnOnce() -> T + Send + 'scope,
232+
T: Send + 'scope,
231233
{
232-
Ok(ScopedJoinHandle(unsafe { self.spawn_unchecked_(|| f(scope), Some(&scope.data)) }?))
234+
Ok(ScopedJoinHandle(unsafe { self.spawn_unchecked_(f, Some(&scope.data)) }?))
233235
}
234236
}
235237

@@ -244,7 +246,7 @@ impl<'scope, T> ScopedJoinHandle<'scope, T> {
244246
/// use std::thread;
245247
///
246248
/// thread::scope(|s| {
247-
/// let t = s.spawn(|_| {
249+
/// let t = s.spawn(|| {
248250
/// println!("hello");
249251
/// });
250252
/// println!("thread id: {:?}", t.thread().id());
@@ -277,7 +279,7 @@ impl<'scope, T> ScopedJoinHandle<'scope, T> {
277279
/// use std::thread;
278280
///
279281
/// thread::scope(|s| {
280-
/// let t = s.spawn(|_| {
282+
/// let t = s.spawn(|| {
281283
/// panic!("oh no");
282284
/// });
283285
/// assert!(t.join().is_err());
@@ -302,7 +304,7 @@ impl<'scope, T> ScopedJoinHandle<'scope, T> {
302304
}
303305
}
304306

305-
impl<'env> fmt::Debug for Scope<'env> {
307+
impl fmt::Debug for Scope<'_, '_> {
306308
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
307309
f.debug_struct("Scope")
308310
.field("num_running_threads", &self.data.num_running_threads.load(Ordering::Relaxed))

0 commit comments

Comments
 (0)