diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bd1cf2aa7..0d16ceceee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -89,6 +89,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `size_hint` not being called from `element::Map`. [#2224](https://github.com/iced-rs/iced/pull/2224) - `size_hint` not being called from `element::Explain`. [#2225](https://github.com/iced-rs/iced/pull/2225) - Slow touch scrolling for `TextEditor` widget. [#2140](https://github.com/iced-rs/iced/pull/2140) +- `Subscription::map` using unreliable function pointer hash to identify mappers. [#2237](https://github.com/iced-rs/iced/pull/2237) Many thanks to... diff --git a/futures/src/subscription.rs b/futures/src/subscription.rs index 7163248dc8..e32227f6e6 100644 --- a/futures/src/subscription.rs +++ b/futures/src/subscription.rs @@ -10,6 +10,7 @@ use crate::{BoxStream, MaybeSend}; use futures::channel::mpsc; use futures::never::Never; +use std::any::TypeId; use std::hash::Hash; /// A stream of runtime events. @@ -88,17 +89,29 @@ impl Subscription { } /// Transforms the [`Subscription`] output with the given function. - pub fn map(mut self, f: fn(Message) -> A) -> Subscription + /// + /// # Panics + /// The closure provided must be a non-capturing closure. The method + /// will panic in debug mode otherwise. + pub fn map(mut self, f: F) -> Subscription where Message: 'static, + F: Fn(Message) -> A + MaybeSend + Clone + 'static, A: 'static, { + debug_assert!( + std::mem::size_of::() == 0, + "the closure {} provided in `Subscription::map` is capturing", + std::any::type_name::(), + ); + Subscription { recipes: self .recipes .drain(..) - .map(|recipe| { - Box::new(Map::new(recipe, f)) as Box> + .map(move |recipe| { + Box::new(Map::new(recipe, f.clone())) + as Box> }) .collect(), } @@ -143,27 +156,34 @@ pub trait Recipe { fn stream(self: Box, input: EventStream) -> BoxStream; } -struct Map { +struct Map +where + F: Fn(A) -> B + 'static, +{ recipe: Box>, - mapper: fn(A) -> B, + mapper: F, } -impl Map { - fn new(recipe: Box>, mapper: fn(A) -> B) -> Self { +impl Map +where + F: Fn(A) -> B + 'static, +{ + fn new(recipe: Box>, mapper: F) -> Self { Map { recipe, mapper } } } -impl Recipe for Map +impl Recipe for Map where A: 'static, B: 'static, + F: Fn(A) -> B + 'static + MaybeSend, { type Output = B; fn hash(&self, state: &mut Hasher) { + TypeId::of::().hash(state); self.recipe.hash(state); - self.mapper.hash(state); } fn stream(self: Box, input: EventStream) -> BoxStream {