/// A scope to spawn scoped threads in.
///
/// See [`scope`] for details.
-pub struct Scope<'env> {
+pub struct Scope<'scope, 'env: 'scope> {
data: ScopeData,
+ /// Invariance over 'scope, to make sure 'scope cannot shrink,
+ /// which is necessary for soundness.
+ ///
+ /// Without invariance, this would compile fine but be unsound:
+ ///
+ /// ```compile_fail,E0373
+ /// #![feature(scoped_threads)]
+ ///
+ /// std::thread::scope(|s| {
+ /// s.spawn(|| {
+ /// let a = String::from("abcd");
+ /// s.spawn(|| println!("{:?}", a)); // might run after `a` is dropped
+ /// });
+ /// });
+ /// ```
+ scope: PhantomData<&'scope mut &'scope ()>,
env: PhantomData<&'env mut &'env ()>,
}
pub struct ScopedJoinHandle<'scope, T>(JoinInner<'scope, T>);
pub(super) struct ScopeData {
- n_running_threads: AtomicUsize,
+ num_running_threads: AtomicUsize,
a_thread_panicked: AtomicBool,
main_thread: Thread,
}
impl ScopeData {
- pub(super) fn increment_n_running_threads(&self) {
+ pub(super) fn increment_num_running_threads(&self) {
// We check for 'overflow' with usize::MAX / 2, to make sure there's no
// chance it overflows to 0, which would result in unsoundness.
- if self.n_running_threads.fetch_add(1, Ordering::Relaxed) > usize::MAX / 2 {
+ if self.num_running_threads.fetch_add(1, Ordering::Relaxed) > usize::MAX / 2 {
// This can only reasonably happen by mem::forget()'ing many many ScopedJoinHandles.
- self.decrement_n_running_threads(false);
+ self.decrement_num_running_threads(false);
panic!("too many running threads in thread scope");
}
}
- pub(super) fn decrement_n_running_threads(&self, panic: bool) {
+ pub(super) fn decrement_num_running_threads(&self, panic: bool) {
if panic {
self.a_thread_panicked.store(true, Ordering::Relaxed);
}
- if self.n_running_threads.fetch_sub(1, Ordering::Release) == 1 {
+ if self.num_running_threads.fetch_sub(1, Ordering::Release) == 1 {
self.main_thread.unpark();
}
}
/// let mut x = 0;
///
/// thread::scope(|s| {
-/// s.spawn(|_| {
+/// s.spawn(|| {
/// println!("hello from the first scoped thread");
/// // We can borrow `a` here.
/// dbg!(&a);
/// });
-/// s.spawn(|_| {
+/// s.spawn(|| {
/// println!("hello from the second scoped thread");
/// // We can even mutably borrow `x` here,
/// // because no other threads are using it.
#[track_caller]
pub fn scope<'env, F, T>(f: F) -> T
where
- F: FnOnce(&Scope<'env>) -> T,
+ F: for<'scope> FnOnce(&'scope Scope<'scope, 'env>) -> T,
{
let scope = Scope {
data: ScopeData {
- n_running_threads: AtomicUsize::new(0),
+ num_running_threads: AtomicUsize::new(0),
main_thread: current(),
a_thread_panicked: AtomicBool::new(false),
},
env: PhantomData,
+ scope: PhantomData,
};
// Run `f`, but catch panics so we can make sure to wait for all the threads to join.
let result = catch_unwind(AssertUnwindSafe(|| f(&scope)));
// Wait until all the threads are finished.
- while scope.data.n_running_threads.load(Ordering::Acquire) != 0 {
+ while scope.data.num_running_threads.load(Ordering::Acquire) != 0 {
park();
}
match result {
Err(e) => resume_unwind(e),
Ok(_) if scope.data.a_thread_panicked.load(Ordering::Relaxed) => {
- panic!("a thread panicked")
+ panic!("a scoped thread panicked")
}
Ok(result) => result,
}
}
-impl<'env> Scope<'env> {
+impl<'scope, 'env> Scope<'scope, 'env> {
/// Spawns a new thread within a scope, returning a [`ScopedJoinHandle`] for it.
///
/// Unlike non-scoped threads, threads spawned with this function may
///
/// # Panics
///
- /// Panics if the OS fails to create a thread; use [`Builder::spawn`]
+ /// Panics if the OS fails to create a thread; use [`Builder::spawn_scoped`]
/// to recover from such errors.
///
/// [`join`]: ScopedJoinHandle::join
- 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,
- T: Send + 'env,
+ F: FnOnce() -> T + Send + 'scope,
+ T: Send + 'scope,
{
Builder::new().spawn_scoped(self, f).expect("failed to spawn thread")
}
/// thread::scope(|s| {
/// thread::Builder::new()
/// .name("first".to_string())
- /// .spawn_scoped(s, |_|
+ /// .spawn_scoped(s, ||
/// {
/// println!("hello from the {:?} scoped thread", thread::current().name());
/// // We can borrow `a` here.
/// .unwrap();
/// thread::Builder::new()
/// .name("second".to_string())
- /// .spawn_scoped(s, |_|
+ /// .spawn_scoped(s, ||
/// {
/// println!("hello from the {:?} scoped thread", thread::current().name());
/// // We can even mutably borrow `x` here,
/// ```
pub fn spawn_scoped<'scope, 'env, F, T>(
self,
- scope: &'scope Scope<'env>,
+ scope: &'scope Scope<'scope, 'env>,
f: F,
) -> io::Result<ScopedJoinHandle<'scope, T>>
where
- F: FnOnce(&Scope<'env>) -> T + Send + 'env,
- T: Send + 'env,
+ F: FnOnce() -> T + Send + 'scope,
+ T: Send + 'scope,
{
- Ok(ScopedJoinHandle(unsafe { self.spawn_unchecked_(|| f(scope), Some(&scope.data)) }?))
+ Ok(ScopedJoinHandle(unsafe { self.spawn_unchecked_(|| f(), Some(&scope.data)) }?))
}
}
/// use std::thread;
///
/// thread::scope(|s| {
- /// let t = s.spawn(|_| {
+ /// let t = s.spawn(|| {
/// println!("hello");
/// });
/// println!("thread id: {:?}", t.thread().id());
/// use std::thread;
///
/// thread::scope(|s| {
- /// let t = s.spawn(|_| {
+ /// let t = s.spawn(|| {
/// panic!("oh no");
/// });
/// assert!(t.join().is_err());
}
}
-impl<'env> fmt::Debug for Scope<'env> {
+impl<'scope, 'env> fmt::Debug for Scope<'scope, 'env> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Scope")
- .field("n_running_threads", &self.data.n_running_threads.load(Ordering::Relaxed))
- .field("a_thread_panicked", &self.data.a_thread_panicked)
+ .field("num_running_threads", &self.data.num_running_threads.load(Ordering::Relaxed))
+ .field("a_thread_panicked", &self.data.a_thread_panicked.load(Ordering::Relaxed))
.field("main_thread", &self.data.main_thread)
.finish_non_exhaustive()
}