Make Parallel<T> more T: !Default accessible (#17943)

# Objective

`ThreadLocal::<T>::default()` doesn't require `T: Default`, so
`Parallel<T>` shouldn't require it either.

## Solution

- Replaced the `Default` derive with a manually specified impl.
- Added `Parallel::borrow_local_mut_or` as a non-`T: Default`-requiring
alternative to `borrow_local_mut`.
- Added `Parallel::scope_or` as a non-`T: Default`-requiring alternative
to `scope`.
This commit is contained in:
Christian Hughes 2025-07-07 15:22:20 -05:00 committed by GitHub
parent 2342e993ec
commit fb5d8fd867
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -5,7 +5,6 @@ use thread_local::ThreadLocal;
/// A cohesive set of thread-local values of a given type.
///
/// Mutable references can be fetched if `T: Default` via [`Parallel::scope`].
#[derive(Default)]
pub struct Parallel<T: Send> {
locals: ThreadLocal<RefCell<T>>,
}
@ -20,6 +19,25 @@ impl<T: Send> Parallel<T> {
pub fn clear(&mut self) {
self.locals.clear();
}
/// Retrieves the thread-local value for the current thread and runs `f` on it.
///
/// If there is no thread-local value, it will be initialized to the result
/// of `create`.
pub fn scope_or<R>(&self, create: impl FnOnce() -> T, f: impl FnOnce(&mut T) -> R) -> R {
f(&mut self.borrow_local_mut_or(create))
}
/// Mutably borrows the thread-local value.
///
/// If there is no thread-local value, it will be initialized to the result
/// of `create`.
pub fn borrow_local_mut_or(
&self,
create: impl FnOnce() -> T,
) -> impl DerefMut<Target = T> + '_ {
self.locals.get_or(|| RefCell::new(create())).borrow_mut()
}
}
impl<T: Default + Send> Parallel<T> {
@ -27,15 +45,14 @@ impl<T: Default + Send> Parallel<T> {
///
/// If there is no thread-local value, it will be initialized to its default.
pub fn scope<R>(&self, f: impl FnOnce(&mut T) -> R) -> R {
let mut cell = self.locals.get_or_default().borrow_mut();
f(cell.deref_mut())
self.scope_or(Default::default, f)
}
/// Mutably borrows the thread-local value.
///
/// If there is no thread-local value, it will be initialized to its default.
pub fn borrow_local_mut(&self) -> impl DerefMut<Target = T> + '_ {
self.locals.get_or_default().borrow_mut()
self.borrow_local_mut_or(Default::default)
}
}
@ -72,3 +89,12 @@ impl<T: Send> Parallel<Vec<T>> {
}
}
}
// `Default` is manually implemented to avoid the `T: Default` bound.
impl<T: Send> Default for Parallel<T> {
fn default() -> Self {
Self {
locals: ThreadLocal::default(),
}
}
}