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}