Assert zero-sized closures in Subscription at compile-time

This commit is contained in:
Héctor Ramón Jiménez 2025-12-02 20:28:51 +01:00
parent c566845091
commit 92ecc18318
No known key found for this signature in database
GPG key ID: 7CC46565708259A7

View file

@ -299,20 +299,14 @@ impl<T> Subscription<T> {
/// Transforms the [`Subscription`] output with the given function. /// Transforms the [`Subscription`] output with the given function.
/// ///
/// # Panics /// The closure provided must be a non-capturing closure.
/// The closure provided must be a non-capturing closure. The method
/// will panic in debug mode otherwise.
pub fn map<F, A>(self, f: F) -> Subscription<A> pub fn map<F, A>(self, f: F) -> Subscription<A>
where where
T: 'static, T: 'static,
F: Fn(T) -> A + MaybeSend + Clone + 'static, F: Fn(T) -> A + MaybeSend + Clone + 'static,
A: 'static, A: 'static,
{ {
debug_assert!( F::assert_zero_size();
std::mem::size_of::<F>() == 0,
"the closure {} provided in `Subscription::map` is capturing",
std::any::type_name::<F>(),
);
struct Map<A, B, F> struct Map<A, B, F>
where where
@ -362,20 +356,14 @@ impl<T> Subscription<T> {
/// Transforms the [`Subscription`] output with the given function, yielding only /// Transforms the [`Subscription`] output with the given function, yielding only
/// values only when the function returns `Some(A)`. /// values only when the function returns `Some(A)`.
/// ///
/// # Panics /// The closure provided must be a non-capturing closure.
/// The closure provided must be a non-capturing closure. The method
/// will panic in debug mode otherwise.
pub fn filter_map<F, A>(mut self, f: F) -> Subscription<A> pub fn filter_map<F, A>(mut self, f: F) -> Subscription<A>
where where
T: MaybeSend + 'static, T: MaybeSend + 'static,
F: Fn(T) -> Option<A> + MaybeSend + Clone + 'static, F: Fn(T) -> Option<A> + MaybeSend + Clone + 'static,
A: MaybeSend + 'static, A: MaybeSend + 'static,
{ {
debug_assert!( F::assert_zero_size();
std::mem::size_of::<F>() == 0,
"the closure {} provided in `Subscription::filter_map` is capturing",
std::any::type_name::<F>(),
);
struct FilterMap<A, B, F> struct FilterMap<A, B, F>
where where
@ -533,3 +521,25 @@ where
crate::boxed_stream((self.spawn)(&self.data, input)) crate::boxed_stream((self.spawn)(&self.data, input))
} }
} }
trait PanicWhenNotZeroSized: Sized {
const _CHECK: () = check_zero_sized::<Self>();
#[allow(path_statements, clippy::no_effect)]
fn assert_zero_size() {
<Self as PanicWhenNotZeroSized>::_CHECK;
}
}
impl<T> PanicWhenNotZeroSized for T {}
const fn check_zero_sized<T>() {
if std::mem::size_of::<T>() != 0 {
panic!(
"The Subscription closure provided is not non-capturing. \
Closures given to Subscription::map or filter_map cannot \
capture external variables. If you need to capture state, \
consider using Subscription::with."
);
}
}