Skip to main content

some_executor/
dyn_executor.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! Dynamic executor implementations with type erasure support.
4//!
5//! This module provides implementations that enable trait objects (`Box<dyn SomeExecutor>`)
6//! to function as executors themselves. This is crucial for storing executors in collections
7//! or passing them around without knowing their concrete types at compile time.
8//!
9//! # Overview
10//!
11//! The type erasure pattern implemented here allows you to use `Box<dyn SomeExecutor>`
12//! as if it were a concrete executor type. This is particularly useful when:
13//!
14//! - You need to store multiple executors of different types
15//! - You want to pass executors across API boundaries without generic parameters
16//! - You're building runtime-configurable executor selection
17//!
18//! # How It Works
19//!
20//! The module implements the executor traits for boxed trait objects, enabling them to:
21//! 1. Accept typed futures and convert them to type-erased versions
22//! 2. Spawn these type-erased futures onto the underlying executor
23//! 3. Return observers that automatically downcast results back to the original type
24//!
25//! # Type Safety
26//!
27//! Despite the type erasure, the implementation maintains type safety through:
28//! - Compile-time type checking when spawning tasks
29//! - Runtime downcasting that panics on type mismatches (programming errors)
30//! - Phantom types to track the original output type
31//!
32//! # Examples
33//!
34//! ## Using a boxed executor
35//!
36//! ```
37//! use some_executor::{SomeExecutor, task::{Task, Configuration}};
38//! use std::future::Future;
39//! use std::convert::Infallible;
40//!
41//! # fn example() {
42//! // Store any executor as a trait object
43//! let mut boxed_executor: Box<dyn SomeExecutor<ExecutorNotifier = Infallible>> =
44//!     todo!(); // Would come from a real executor implementation
45//!
46//! // Use it just like a concrete executor
47//! let task = Task::without_notifications(
48//!     "example".to_string(),
49//!     Configuration::default(),
50//!     async { 42 }
51//! );
52//! # // Can't actually spawn without a real executor
53//! # // let observer = boxed_executor.spawn(task);
54//! # }
55//! ```
56//!
57//! ## Storing multiple executor types
58//!
59//! ```
60//! use some_executor::SomeExecutor;
61//! use std::convert::Infallible;
62//!
63//! struct ExecutorPool {
64//!     executors: Vec<Box<dyn SomeExecutor<ExecutorNotifier = Infallible>>>,
65//! }
66//!
67//! impl ExecutorPool {
68//!     fn add_executor(&mut self, executor: Box<dyn SomeExecutor<ExecutorNotifier = Infallible>>) {
69//!         self.executors.push(executor);
70//!     }
71//!     
72//!     fn get_executor(&mut self) -> Option<&mut Box<dyn SomeExecutor<ExecutorNotifier = Infallible>>> {
73//!         self.executors.first_mut()
74//!     }
75//! }
76//! ```
77
78use crate::dyn_observer::DowncastObserver;
79use crate::observer::{
80    ExecutorNotified, FinishedObservation, Observation, Observer, ObserverNotified,
81};
82use crate::task::{Task, TaskID};
83use crate::{DynExecutor, DynStaticExecutor, SomeExecutor, SomeStaticExecutor};
84use std::any::Any;
85use std::future::Future;
86use std::marker::PhantomData;
87use std::pin::Pin;
88use std::task::{Context, Poll};
89
90/// Observer that downcasts type-erased results back to their original type for static executors.
91///
92/// This is the non-Send version of [`DowncastObserver`], used with [`SomeStaticExecutor`]
93/// implementations that work with `!Send` futures. It performs runtime downcasting of
94/// `Box<dyn Any + 'static>` values back to the concrete type `V`.
95///
96/// # Type Parameters
97///
98/// - `O`: The underlying observer type that produces `Box<dyn Any + 'static>` values
99/// - `V`: The concrete type to downcast to
100///
101/// # Panics
102///
103/// Methods will panic if downcasting fails, which indicates a type mismatch bug in the
104/// executor implementation.
105pub struct StaticDowncastObserver<O, V>(O, PhantomData<V>);
106
107impl<O, V> Observer for StaticDowncastObserver<O, V>
108where
109    O: Observer<Value = Box<dyn Any + 'static>> + 'static,
110    V: 'static,
111{
112    type Value = V;
113
114    /// Observes the current state of the task and downcasts the result if ready.
115    ///
116    /// # Panics
117    ///
118    /// Panics if the task result is ready but cannot be downcast to type `V`.
119    /// This indicates a type system violation in the executor implementation.
120    fn observe(&self) -> Observation<Self::Value> {
121        let inner = self.0.observe();
122        match inner {
123            Observation::Pending => Observation::Pending,
124            Observation::Ready(e) => {
125                let downcasted = e.downcast::<V>().expect("Downcast failed");
126                Observation::Ready(*downcasted)
127            }
128            Observation::Done => Observation::Done,
129            Observation::Cancelled => Observation::Cancelled,
130        }
131    }
132
133    /// Returns the task ID of the underlying observer.
134    fn task_id(&self) -> &TaskID {
135        self.0.task_id()
136    }
137}
138
139impl<O, V> StaticDowncastObserver<O, V> {
140    /// Creates a new `StaticDowncastObserver` wrapping the given observer.
141    ///
142    /// # Arguments
143    ///
144    /// * `observer` - The underlying observer that produces type-erased values
145    ///
146    /// # Examples
147    ///
148    /// ```compile_fail
149    /// // ALLOW_COMPILE_FAIL_DOCTEST: This shows the API but requires internal types
150    /// // The StaticDowncastObserver is not exported publicly
151    /// use some_executor::dyn_executor::StaticDowncastObserver;
152    /// use some_executor::observer::{Observer, Observation};
153    /// use std::any::Any;
154    ///
155    /// struct MockObserver;
156    /// // Observer requires Future trait which MockObserver doesn't implement
157    /// // This is just to show the API usage
158    /// let mock_observer = /* ... */;
159    /// let typed_observer: StaticDowncastObserver<_, i32> =
160    ///     StaticDowncastObserver::new(mock_observer);
161    /// ```
162    pub fn new(observer: O) -> Self {
163        Self(observer, PhantomData)
164    }
165}
166
167impl<O, V> Future for StaticDowncastObserver<O, V>
168where
169    O: Future<Output = FinishedObservation<Box<dyn Any + 'static>>> + 'static,
170    V: 'static,
171{
172    type Output = FinishedObservation<V>;
173
174    /// Polls the underlying future and downcasts the result if ready.
175    ///
176    /// # Panics
177    ///
178    /// Panics if the future completes with a value that cannot be downcast to type `V`.
179    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
180        let f = unsafe { self.map_unchecked_mut(|s| &mut s.0) }.poll(cx);
181        match f {
182            Poll::Pending => Poll::Pending,
183            Poll::Ready(e) => match e {
184                FinishedObservation::Cancelled => Poll::Ready(FinishedObservation::Cancelled),
185                FinishedObservation::Ready(e) => {
186                    let downcasted = e.downcast::<V>().expect("Downcast failed");
187                    Poll::Ready(FinishedObservation::Ready(*downcasted))
188                }
189            },
190        }
191    }
192}
193
194/// Implementation of `SomeExecutor` for boxed executor trait objects.
195///
196/// This implementation enables `Box<dyn SomeExecutor>` to act as an executor itself,
197/// providing the crucial type erasure pattern that allows executors to be stored and
198/// used polymorphically.
199///
200/// # How It Works
201///
202/// When you spawn a task on a boxed executor:
203/// 1. The concrete future type is converted to a type-erased version using `into_objsafe()`
204/// 2. The type-erased task is spawned on the underlying executor
205/// 3. A `DowncastObserver` is returned that will convert results back to the original type
206///
207/// # Examples
208///
209/// ```
210/// # use some_executor::{SomeExecutor, task::{Task, Configuration}};
211/// # use std::convert::Infallible;
212/// # fn example() {
213/// let mut executor: Box<dyn SomeExecutor<ExecutorNotifier = Infallible>> = todo!();
214///
215/// // Spawn a typed future
216/// let task = Task::without_notifications(
217///     "hello-task".to_string(),
218///     Configuration::default(),
219///     async { "hello".to_string() }
220/// );
221/// # // Would work with a real executor:
222/// # // let observer = executor.spawn(task);
223/// # // assert_eq!(observer.await.unwrap(), "hello");
224/// # }
225/// ```
226impl<UnderlyingNotifier: ExecutorNotified + Send> SomeExecutor
227    for Box<dyn SomeExecutor<ExecutorNotifier = UnderlyingNotifier>>
228{
229    type ExecutorNotifier = Box<dyn ExecutorNotified + Send>;
230
231    /// Spawns a future onto the underlying executor with automatic type erasure and recovery.
232    ///
233    /// This method handles the type erasure process transparently, converting the typed
234    /// future into a type-erased version for the underlying executor, then wrapping the
235    /// returned observer to restore type information.
236    fn spawn<F: Future + Send + 'static, Notifier: ObserverNotified<F::Output> + Send>(
237        &mut self,
238        task: Task<F, Notifier>,
239    ) -> impl Observer<Value = F::Output>
240    where
241        Self: Sized,
242        F::Output: Send + Unpin,
243    {
244        let underlying = self.as_mut();
245        let objsafe = task.into_objsafe();
246        let observer = underlying.spawn_objsafe(objsafe);
247        // Restore the concrete type through downcasting
248        let downcasted: DowncastObserver<_, F::Output> = DowncastObserver::new(observer);
249        downcasted
250    }
251
252    /// Asynchronously spawns a future onto the underlying executor.
253    ///
254    /// Similar to `spawn`, but allows the spawning process itself to be asynchronous.
255    async fn spawn_async<F: Future + Send + 'static, Notifier: ObserverNotified<F::Output> + Send>(
256        &mut self,
257        task: Task<F, Notifier>,
258    ) -> impl Observer<Value = F::Output>
259    where
260        Self: Sized,
261        F::Output: Send + Unpin,
262    {
263        let underlying = self.as_mut();
264        let objsafe = task.into_objsafe();
265        let observer = underlying.spawn_objsafe(objsafe);
266        // Restore the concrete type through downcasting
267        let downcasted: DowncastObserver<_, F::Output> = DowncastObserver::new(observer);
268        downcasted
269    }
270
271    /// Spawns an already type-erased task.
272    ///
273    /// This is the object-safe method that works with type-erased futures directly.
274    /// User code typically doesn't call this; it's used internally by the typed spawn methods.
275    fn spawn_objsafe(
276        &mut self,
277        task: Task<
278            Pin<Box<dyn Future<Output = Box<dyn Any + 'static + Send>> + 'static + Send>>,
279            Box<dyn ObserverNotified<dyn Any + Send> + Send>,
280        >,
281    ) -> Box<
282        dyn Observer<Value = Box<dyn Any + Send>, Output = FinishedObservation<Box<dyn Any + Send>>>
283            + Send,
284    > {
285        self.as_mut().spawn_objsafe(task)
286    }
287
288    /// Asynchronously spawns an already type-erased task.
289    ///
290    /// The async version of `spawn_objsafe` for executors that need async initialization.
291    fn spawn_objsafe_async<'s>(
292        &'s mut self,
293        task: Task<
294            Pin<Box<dyn Future<Output = Box<dyn Any + 'static + Send>> + 'static + Send>>,
295            Box<dyn ObserverNotified<dyn Any + Send> + Send>,
296        >,
297    ) -> Box<
298        dyn Future<
299                Output = Box<
300                    dyn Observer<
301                            Value = Box<dyn Any + Send>,
302                            Output = FinishedObservation<Box<dyn Any + Send>>,
303                        > + Send,
304                >,
305            > + 's,
306    > {
307        self.as_mut().spawn_objsafe_async(task)
308    }
309
310    /// Creates a boxed clone of the executor.
311    ///
312    /// This allows the boxed executor to be cloned while preserving the type erasure.
313    fn clone_box(&self) -> Box<DynExecutor> {
314        self.as_ref().clone_box()
315    }
316
317    /// Gets a notifier for executor-level events, if supported.
318    ///
319    /// Returns a type-erased notifier that can be used to wake the executor.
320    fn executor_notifier(&mut self) -> Option<Self::ExecutorNotifier> {
321        let underlying = self.as_mut().executor_notifier();
322        underlying.map(|u| Box::new(u) as Box<dyn ExecutorNotified + Send>)
323    }
324}
325
326/// Implementation of `SomeStaticExecutor` for boxed static executor trait objects.
327///
328/// This implementation enables `Box<dyn SomeStaticExecutor>` to act as a static executor,
329/// supporting `!Send` futures through type erasure. This is particularly useful for
330/// single-threaded or local executors.
331///
332/// # Differences from `SomeExecutor`
333///
334/// Unlike the `SomeExecutor` implementation, this works with futures that are not `Send`,
335/// making it suitable for JavaScript/WASM environments or thread-local execution contexts.
336///
337/// # Examples
338///
339/// ```
340/// # use some_executor::{SomeStaticExecutor, task::{Task, Configuration}};
341/// # use std::convert::Infallible;
342/// # use std::rc::Rc;
343/// # fn example() {
344/// let mut executor: Box<dyn SomeStaticExecutor<ExecutorNotifier = Infallible>> = todo!();
345///
346/// // Can spawn !Send futures
347/// let rc = Rc::new(42);
348/// let task = Task::without_notifications(
349///     "rc-task".to_string(),
350///     Configuration::default(),
351///     async move { *rc }
352/// );
353/// # // Would work with a real executor:
354/// # // let observer = executor.spawn_static(task);
355/// # }
356/// ```
357impl<UnderlyingNotifier: ExecutorNotified> SomeStaticExecutor
358    for Box<dyn SomeStaticExecutor<ExecutorNotifier = UnderlyingNotifier>>
359{
360    type ExecutorNotifier = Box<dyn ExecutorNotified>;
361
362    /// Spawns a static (potentially !Send) future onto the underlying executor.
363    ///
364    /// This method supports futures that don't implement `Send`, making it suitable
365    /// for single-threaded environments.
366    fn spawn_static<F, Notifier: ObserverNotified<F::Output>>(
367        &mut self,
368        task: Task<F, Notifier>,
369    ) -> impl Observer<Value = F::Output>
370    where
371        Self: Sized,
372        F: Future + 'static,
373        F::Output: 'static + Unpin,
374    {
375        let underlying = self.as_mut();
376        let objsafe = task.into_objsafe_static();
377        let observer = underlying.spawn_static_objsafe(objsafe);
378        // Use StaticDowncastObserver for non-Send type recovery
379        StaticDowncastObserver::new(observer)
380    }
381
382    /// Asynchronously spawns a static future onto the underlying executor.
383    ///
384    /// The async version of `spawn_static` for executors that need async initialization.
385    async fn spawn_static_async<F, Notifier: ObserverNotified<F::Output>>(
386        &mut self,
387        task: Task<F, Notifier>,
388    ) -> impl Observer<Value = F::Output>
389    where
390        Self: Sized,
391        F: Future + 'static,
392        F::Output: 'static + Unpin,
393    {
394        let underlying = self.as_mut();
395        let objsafe = task.into_objsafe_static();
396        let observer = underlying.spawn_static_objsafe(objsafe);
397        StaticDowncastObserver::new(observer)
398    }
399
400    /// Spawns an already type-erased static task.
401    ///
402    /// This is the object-safe method for static executors. User code typically
403    /// doesn't call this directly.
404    fn spawn_static_objsafe(
405        &mut self,
406        task: crate::ObjSafeStaticTask,
407    ) -> crate::BoxedStaticObserver {
408        self.as_mut().spawn_static_objsafe(task)
409    }
410
411    /// Asynchronously spawns an already type-erased static task.
412    ///
413    /// The async version of `spawn_static_objsafe`.
414    fn spawn_static_objsafe_async<'s>(
415        &'s mut self,
416        task: crate::ObjSafeStaticTask,
417    ) -> crate::BoxedStaticObserverFuture<'s> {
418        self.as_mut().spawn_static_objsafe_async(task)
419    }
420
421    /// Creates a boxed clone of the static executor.
422    fn clone_box(&self) -> Box<DynStaticExecutor> {
423        self.as_ref().clone_box()
424    }
425
426    /// Gets a notifier for executor-level events, if supported.
427    fn executor_notifier(&mut self) -> Option<Self::ExecutorNotifier> {
428        let underlying = self.as_mut().executor_notifier();
429        underlying.map(|u| Box::new(u) as Box<dyn ExecutorNotified>)
430    }
431}
432
433#[cfg(test)]
434mod tests {
435    use crate::{Infallible, SomeExecutor};
436
437    #[cfg(target_arch = "wasm32")]
438    use wasm_bindgen_test::*;
439
440    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
441    #[cfg_attr(not(target_arch = "wasm32"), test)]
442    fn test_dyn_executor() {
443        //mt2-697
444        #[allow(dead_code)]
445        fn just_compile() {
446            let _ty: Box<dyn SomeExecutor<ExecutorNotifier = Infallible>> = todo!();
447            #[allow(unreachable_code, unused_variables)]
448            fn expect_executor<E: SomeExecutor>(e: E) {}
449            #[allow(unreachable_code)]
450            expect_executor(_ty);
451        }
452    }
453}