executor_core/
lib.rs

1//! # executor-core
2//!
3//! A flexible task executor abstraction layer for Rust async runtimes.
4//!
5//! This crate provides unified traits and type-erased wrappers for different async executors,
6//! allowing you to write code that's agnostic to the underlying executor implementation.
7//!
8//! ## Overview
9//!
10//! The crate is built around two main traits:
11//! - [`Executor`]: For spawning `Send + 'static` futures
12//! - [`LocalExecutor`]: For spawning `'static` futures (not necessarily `Send`)
13//!
14//! Both traits produce tasks that implement the [`Task`] trait, providing:
15//! - [`Future`] implementation for awaiting results
16//! - [`Task::poll_result`] for explicit error handling
17//! - [`Task::poll_cancel`] for task cancellation
18//!
19//! ## Quick Start
20//!
21//! ```rust
22//! use executor_core::{Executor, init_global_executor, spawn};
23//! use executor_core::tokio::TokioExecutor;
24//!
25//! #[tokio::main]
26//! async fn main() {
27//!     // Initialize the global executor
28//!     init_global_executor(TokioExecutor::new());
29//!
30//!     // Spawn a task
31//!     let task = spawn(async {
32//!         println!("Hello from spawned task!");
33//!         42
34//!     });
35//!
36//!     // The task can be awaited to get the result
37//!     let result = task.await;
38//!     println!("Task result: {}", result);
39//! }
40//! ```
41//!
42//! ## Features
43//!
44//! - **Zero-cost Executor Abstraction**: Unified [`Executor`] and [`LocalExecutor`] traits
45//!   using Generic Associated Types (GAT) to prevent unnecessary heap allocation and dynamic dispatch
46//! - **Type Erasure**: [`AnyExecutor`] and [`AnyLocalExecutor`] for runtime flexibility
47//! - **Multiple Runtime Support**: Tokio, async-executor, Web/WASM
48//! - **Task Management**: Rich task API with cancellation and error handling
49//! - **No-std Compatible**: Core functionality works in no-std environments
50//! - **Panic Safety**: Proper panic handling and propagation
51//!
52//! ## Lifetime Constraints
53//!
54//! The current API requires `'static` lifetimes for both futures and their outputs.
55//! This constraint comes from the underlying async runtimes and ensures memory safety
56//! when tasks may outlive their spawning scope. While this limits flexibility, it
57//! matches the constraints of most async runtime implementations in Rust.
58
59#![no_std]
60#![cfg_attr(docsrs, feature(doc_cfg))]
61#![warn(missing_docs, missing_debug_implementations)]
62
63#[cfg(feature = "async-task")]
64#[cfg_attr(docsrs, doc(cfg(feature = "async-task")))]
65pub mod async_task;
66
67#[cfg(feature = "async-executor")]
68#[cfg_attr(docsrs, doc(cfg(feature = "async-executor")))]
69pub mod async_executor;
70
71#[cfg(feature = "tokio")]
72#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
73pub mod tokio;
74
75#[cfg(feature = "web")]
76#[cfg_attr(docsrs, doc(cfg(feature = "web")))]
77pub mod web;
78
79use core::{
80    any::Any,
81    fmt::Debug,
82    future::Future,
83    marker::PhantomData,
84    panic::AssertUnwindSafe,
85    pin::Pin,
86    task::{Context, Poll},
87};
88
89use alloc::boxed::Box;
90use async_channel::Receiver;
91
92extern crate alloc;
93
94/// A trait for spawning `Send + 'static` futures.
95///
96/// This trait is implemented by runtime-agnostic executors that can spawn futures
97/// across thread boundaries. The spawned futures must be `Send` and `'static`.
98///
99/// The `'static` lifetime requirements come from the underlying async runtimes
100/// (like Tokio) which need to ensure memory safety when tasks are moved across
101/// threads and may outlive their spawning scope.
102///
103/// See [AnyExecutor] for a type-erased executor.
104#[cfg_attr(feature = "web", doc = "- [`web::WebExecutor`] for WASM environments")]
105pub trait Executor: Send + Sync {
106    /// The task type returned by [`spawn`](Self::spawn).
107    ///
108    /// The `T: Send + 'static` constraint ensures the task output can be safely
109    /// sent across thread boundaries and doesn't contain any borrowed data.
110    type Task<T: Send + 'static>: Task<T> + Send;
111
112    /// Spawn a future that will run to completion.
113    ///
114    /// The future must be `Send + 'static` to ensure it can be moved across threads.
115    /// Returns a [`Task`] that can be awaited to get the result.
116    fn spawn<Fut>(&self, fut: Fut) -> Self::Task<Fut::Output>
117    where
118        Fut: Future<Output: Send> + Send + 'static;
119}
120
121impl<E: Executor> Executor for &E {
122    type Task<T: Send + 'static> = E::Task<T>;
123
124    fn spawn<Fut>(&self, fut: Fut) -> Self::Task<Fut::Output>
125    where
126        Fut: Future<Output: Send> + Send + 'static,
127    {
128        (*self).spawn(fut)
129    }
130}
131
132impl<E: Executor> Executor for &mut E {
133    type Task<T: Send + 'static> = E::Task<T>;
134
135    fn spawn<Fut>(&self, fut: Fut) -> Self::Task<Fut::Output>
136    where
137        Fut: Future<Output: Send> + Send + 'static,
138    {
139        (**self).spawn(fut)
140    }
141}
142
143impl<E: Executor> Executor for Box<E> {
144    type Task<T: Send + 'static> = E::Task<T>;
145
146    fn spawn<Fut>(&self, fut: Fut) -> Self::Task<Fut::Output>
147    where
148        Fut: Future<Output: Send> + Send + 'static,
149    {
150        (**self).spawn(fut)
151    }
152}
153
154impl<E: Executor> Executor for alloc::sync::Arc<E> {
155    type Task<T: Send + 'static> = E::Task<T>;
156
157    fn spawn<Fut>(&self, fut: Fut) -> Self::Task<Fut::Output>
158    where
159        Fut: Future<Output: Send> + Send + 'static,
160    {
161        (**self).spawn(fut)
162    }
163}
164
165/// A trait for spawning `'static` futures that may not be `Send`.
166///
167/// This trait is for executors that can spawn futures that don't need to be `Send`,
168/// typically single-threaded executors or local task spawners.
169///
170/// The `'static` lifetime requirements come from the underlying async runtimes
171/// which need to ensure memory safety when tasks may outlive their spawning scope,
172/// even in single-threaded contexts.
173///
174/// See [AnyLocalExecutor] for a type-erased local executor.
175#[cfg_attr(feature = "web", doc = "- [`web::WebExecutor`] for WASM environments")]
176pub trait LocalExecutor {
177    /// The task type returned by [`spawn`](Self::spawn).
178    ///
179    /// The `T: 'static` constraint ensures the task output doesn't contain
180    /// any borrowed data that could become invalid.
181    type Task<T: 'static>: Task<T>;
182
183    /// Spawn a future that will run to completion on the local executor.
184    ///
185    /// The future must be `'static` but does not need to be `Send`.
186    /// Returns a [`Task`] that can be awaited to get the result.
187    fn spawn_local<Fut>(&self, fut: Fut) -> Self::Task<Fut::Output>
188    where
189        Fut: Future + 'static;
190}
191
192impl<E: LocalExecutor> LocalExecutor for &E {
193    type Task<T: 'static> = E::Task<T>;
194
195    fn spawn_local<Fut>(&self, fut: Fut) -> Self::Task<Fut::Output>
196    where
197        Fut: Future + 'static,
198    {
199        (*self).spawn_local(fut)
200    }
201}
202
203impl<E: LocalExecutor> LocalExecutor for &mut E {
204    type Task<T: 'static> = E::Task<T>;
205
206    fn spawn_local<Fut>(&self, fut: Fut) -> Self::Task<Fut::Output>
207    where
208        Fut: Future + 'static,
209    {
210        (**self).spawn_local(fut)
211    }
212}
213
214impl<E: LocalExecutor> LocalExecutor for Box<E> {
215    type Task<T: 'static> = E::Task<T>;
216
217    fn spawn_local<Fut>(&self, fut: Fut) -> Self::Task<Fut::Output>
218    where
219        Fut: Future + 'static,
220    {
221        (**self).spawn_local(fut)
222    }
223}
224
225impl<E: LocalExecutor> LocalExecutor for alloc::rc::Rc<E> {
226    type Task<T: 'static> = E::Task<T>;
227
228    fn spawn_local<Fut>(&self, fut: Fut) -> Self::Task<Fut::Output>
229    where
230        Fut: Future + 'static,
231    {
232        (**self).spawn_local(fut)
233    }
234}
235
236impl<E: LocalExecutor> LocalExecutor for alloc::sync::Arc<E> {
237    type Task<T: 'static> = E::Task<T>;
238
239    fn spawn_local<Fut>(&self, fut: Fut) -> Self::Task<Fut::Output>
240    where
241        Fut: Future + 'static,
242    {
243        (**self).spawn_local(fut)
244    }
245}
246
247trait AnyLocalExecutorImpl: 'static + Any {
248    fn spawn_local_boxed(
249        &self,
250        fut: Pin<Box<dyn Future<Output = ()>>>,
251    ) -> Pin<Box<dyn Task<()> + 'static>>;
252}
253
254impl<E> AnyLocalExecutorImpl for E
255where
256    E: LocalExecutor + 'static,
257{
258    fn spawn_local_boxed(
259        &self,
260        fut: Pin<Box<dyn Future<Output = ()>>>,
261    ) -> Pin<Box<dyn Task<()> + 'static>> {
262        let task = self.spawn_local(fut);
263        Box::pin(task)
264    }
265}
266
267/// A type-erased [`LocalExecutor`] that can hold any local executor implementation.
268///
269/// This allows for runtime selection of executors and storing different executor
270/// types in the same collection.
271pub struct AnyLocalExecutor(Box<dyn AnyLocalExecutorImpl>);
272
273impl Debug for AnyLocalExecutor {
274    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
275        f.debug_struct("AnyLocalExecutor").finish()
276    }
277}
278
279impl Debug for AnyExecutor {
280    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
281        f.debug_struct("AnyExecutor").finish()
282    }
283}
284
285impl AnyExecutor {
286    /// Create a new [`AnyExecutor`] wrapping the given executor.
287    pub fn new(executor: impl Executor + 'static) -> Self {
288        Self(Box::new(executor))
289    }
290
291    /// Attempt to downcast to a concrete executor type by reference.
292    ///
293    /// Returns `Some(&E)` if the underlying executor is of type `E`, `None` otherwise.
294    pub fn downcast_ref<E: Executor + 'static>(&self) -> Option<&E> {
295        let any: &dyn Any = self.0.as_ref();
296
297        any.downcast_ref()
298    }
299
300    /// Attempt to downcast to a concrete executor type by value.
301    ///
302    /// Returns `Ok(Box<E>)` if the underlying executor is of type `E`,
303    /// `Err(Self)` otherwise (returning the original `AnyExecutor`).
304    pub fn downcast<E: Executor + 'static>(self) -> Result<Box<E>, Self> {
305        if (&self.0 as &dyn Any).is::<E>() {
306            Ok((self.0 as Box<dyn Any>).downcast().ok().unwrap())
307        } else {
308            Err(self)
309        }
310    }
311}
312
313impl AnyLocalExecutor {
314    /// Create a new [`AnyLocalExecutor`] wrapping the given local executor.
315    pub fn new(executor: impl LocalExecutor + 'static) -> Self {
316        Self(Box::new(executor))
317    }
318
319    /// Attempt to downcast to a concrete local executor type by reference.
320    ///
321    /// Returns `Some(&E)` if the underlying executor is of type `E`, `None` otherwise.
322    pub fn downcast_ref<E: LocalExecutor + 'static>(&self) -> Option<&E> {
323        let any: &dyn Any = self.0.as_ref();
324
325        any.downcast_ref()
326    }
327
328    /// Attempt to downcast to a concrete local executor type by value.
329    ///
330    /// Returns `Ok(Box<E>)` if the underlying executor is of type `E`,
331    /// `Err(Self)` otherwise (returning the original `AnyLocalExecutor`).
332    pub fn downcast<E: LocalExecutor + 'static>(self) -> Result<Box<E>, Self> {
333        if (&self.0 as &dyn Any).is::<E>() {
334            Ok((self.0 as Box<dyn Any>).downcast().ok().unwrap())
335        } else {
336            Err(self)
337        }
338    }
339}
340
341/// Task type returned by [`AnyLocalExecutor`].
342///
343/// This task can be awaited like any other task and provides the same
344/// cancellation and error handling capabilities as other task implementations.
345/// It wraps tasks from any [`LocalExecutor`] implementation in a type-erased manner.
346pub struct AnyLocalExecutorTask<T> {
347    inner: Pin<Box<dyn Task<()> + 'static>>,
348    receiver: Receiver<Result<T, Error>>,
349}
350
351impl<T> core::fmt::Debug for AnyLocalExecutorTask<T> {
352    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
353        f.debug_struct("AnyLocalExecutorTask")
354            .finish_non_exhaustive()
355    }
356}
357
358impl<T> Future for AnyLocalExecutorTask<T> {
359    type Output = T;
360
361    fn poll(
362        self: Pin<&mut Self>,
363        cx: &mut core::task::Context<'_>,
364    ) -> core::task::Poll<Self::Output> {
365        self.poll_result(cx).map(|res| res.unwrap())
366    }
367}
368
369impl<T> Task<T> for AnyLocalExecutorTask<T> {
370    fn poll_result(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<T, Error>> {
371        // First, ensure the underlying task is being polled
372        let this = unsafe { self.get_unchecked_mut() };
373        let _ = this.inner.as_mut().poll(cx);
374
375        // Then poll the receiver
376        let mut recv = this.receiver.recv();
377        unsafe {
378            Pin::new_unchecked(&mut recv)
379                .poll(cx)
380                .map(|res| res.unwrap_or_else(|_| Err(Box::new("Channel closed"))))
381        }
382    }
383    fn poll_cancel(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
384        let this = unsafe { self.get_unchecked_mut() };
385        this.inner.as_mut().poll_cancel(cx)
386    }
387}
388
389impl LocalExecutor for AnyLocalExecutor {
390    type Task<T: 'static> = AnyLocalExecutorTask<T>;
391
392    fn spawn_local<Fut>(&self, fut: Fut) -> Self::Task<Fut::Output>
393    where
394        Fut: Future + 'static,
395    {
396        let (sender, receiver) = async_channel::bounded(1);
397        let fut = async move {
398            let res = AssertUnwindSafe(fut).await;
399            let _ = sender.send(Ok(res)).await;
400        };
401        let inner = self.0.spawn_local_boxed(Box::pin(fut));
402        AnyLocalExecutorTask { inner, receiver }
403    }
404}
405
406/// Type alias for errors that can occur during task execution.
407///
408/// This represents panics or other unrecoverable errors from spawned tasks.
409type Error = Box<dyn core::any::Any + Send>;
410
411/// A trait representing a spawned task that can be awaited, cancelled, or queried for results.
412///
413/// This trait extends [`Future`] with additional capabilities for task management:
414/// - Explicit error handling via [`poll_result`](Self::poll_result)
415/// - Task cancellation via [`poll_cancel`](Self::poll_cancel)
416/// - Convenience methods for getting results and cancelling
417///
418pub trait Task<T>: Future<Output = T> {
419    /// Poll the task for completion, returning a [`Result`] that can contain errors.
420    ///
421    /// Unlike the [`Future::poll`] implementation, this method allows you to handle
422    /// task panics and other errors explicitly rather than propagating them.
423    fn poll_result(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<T, Error>>;
424
425    /// Poll for task cancellation.
426    ///
427    /// This method attempts to cancel the task and returns [`Poll::Ready`] when
428    /// the cancellation is complete.
429    fn poll_cancel(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()>;
430
431    /// Get the result of the task, including any errors that occurred.
432    ///
433    /// This is equivalent to awaiting the task but returns a [`Result`] that
434    /// allows you to handle panics and other errors explicitly.
435    ///
436    fn result(self) -> impl Future<Output = Result<T, Error>>
437    where
438        Self: Sized,
439    {
440        ResultFuture {
441            task: self,
442            _phantom: PhantomData,
443        }
444    }
445
446    /// Detach the task, allowing it to run in the background without being awaited.
447    ///
448    /// Once detached, the task will continue running but its result cannot be retrieved.
449    /// This is useful for fire-and-forget operations where you don't need to wait for
450    /// or handle the result.
451    fn detach(self)
452    where
453        Self: Sized,
454    {
455        // Simply drop the task to detach it
456        // Just shut up compiler about unused self :)
457        core::mem::drop(self);
458    }
459
460    /// Cancel the task.
461    ///
462    /// This method requests cancellation of the task and returns a future that
463    /// completes when the cancellation is finished.
464    ///
465    fn cancel(self) -> impl Future<Output = ()>
466    where
467        Self: Sized,
468    {
469        CancelFuture {
470            task: self,
471            _phantom: PhantomData,
472        }
473    }
474}
475
476/// Future returned by [`Task::result()`].
477///
478/// This future resolves to a `Result<T, Error>` when the underlying task completes,
479/// allowing explicit handling of task panics and other errors without propagating them.
480pub struct ResultFuture<T: Task<U>, U> {
481    task: T,
482    _phantom: PhantomData<U>,
483}
484
485impl<T: Task<U>, U> core::fmt::Debug for ResultFuture<T, U> {
486    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
487        f.debug_struct("ResultFuture").finish_non_exhaustive()
488    }
489}
490
491impl<T: Task<U>, U> Future for ResultFuture<T, U> {
492    type Output = Result<U, Error>;
493
494    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
495        let this = unsafe { self.get_unchecked_mut() };
496        unsafe { Pin::new_unchecked(&mut this.task) }.poll_result(cx)
497    }
498}
499
500/// Future returned by [`Task::cancel()`].
501///
502/// This future resolves when the underlying task cancellation is complete.
503/// The future completes regardless of whether the task was successfully cancelled
504/// or had already finished.
505pub struct CancelFuture<T: Task<U>, U> {
506    task: T,
507    _phantom: PhantomData<U>,
508}
509
510impl<T: Task<U>, U> core::fmt::Debug for CancelFuture<T, U> {
511    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
512        f.debug_struct("CancelFuture").finish_non_exhaustive()
513    }
514}
515
516impl<T: Task<U>, U> Future for CancelFuture<T, U> {
517    type Output = ();
518
519    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
520        let this = unsafe { self.get_unchecked_mut() };
521        unsafe { Pin::new_unchecked(&mut this.task) }.poll_cancel(cx)
522    }
523}
524
525/// A type-erased [`Executor`] that can hold any executor implementation.
526///
527/// This allows for runtime selection of executors and storing different executor
528/// types in the same collection.
529///
530pub struct AnyExecutor(Box<dyn AnyExecutorImpl>);
531
532/// Task type returned by [`AnyExecutor`].
533///
534/// This task can be awaited like any other task and provides the same
535/// cancellation and error handling capabilities.
536pub struct AnyExecutorTask<T> {
537    inner: Pin<Box<dyn Task<()> + Send>>,
538    receiver: Receiver<Result<T, Error>>,
539}
540
541impl<T> core::fmt::Debug for AnyExecutorTask<T> {
542    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
543        f.debug_struct("AnyExecutorTask").finish_non_exhaustive()
544    }
545}
546
547impl<T: Send> Future for AnyExecutorTask<T> {
548    type Output = T;
549
550    fn poll(
551        self: Pin<&mut Self>,
552        cx: &mut core::task::Context<'_>,
553    ) -> core::task::Poll<Self::Output> {
554        self.poll_result(cx).map(|res| res.unwrap())
555    }
556}
557
558impl<T: Send> Task<T> for AnyExecutorTask<T> {
559    fn poll_result(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<T, Error>> {
560        // First, ensure the underlying task is being polled
561        let this = unsafe { self.get_unchecked_mut() };
562        let _ = this.inner.as_mut().poll(cx);
563
564        // Then poll the receiver
565        let mut recv = this.receiver.recv();
566        unsafe {
567            Pin::new_unchecked(&mut recv)
568                .poll(cx)
569                .map(|res| res.unwrap_or_else(|_| Err(Box::new("Channel closed"))))
570        }
571    }
572    fn poll_cancel(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
573        let this = unsafe { self.get_unchecked_mut() };
574        this.inner.as_mut().poll_cancel(cx)
575    }
576}
577
578impl Executor for AnyExecutor {
579    type Task<T: Send + 'static> = AnyExecutorTask<T>;
580
581    fn spawn<Fut>(&self, fut: Fut) -> Self::Task<Fut::Output>
582    where
583        Fut: Future<Output: Send> + Send + 'static,
584    {
585        let (sender, receiver) = async_channel::bounded(1);
586        let fut = async move {
587            let res = AssertUnwindSafe(fut).await;
588            let _ = sender.send(Ok(res)).await;
589        };
590        let inner = self.0.spawn_boxed(Box::pin(fut));
591        AnyExecutorTask { inner, receiver }
592    }
593}
594
595trait AnyExecutorImpl: Send + Sync + Any {
596    fn spawn_boxed(
597        &self,
598        fut: Pin<Box<dyn Future<Output = ()> + Send>>,
599    ) -> Pin<Box<dyn Task<()> + Send>>;
600}
601
602impl<T: Task<T>> Task<T> for Pin<Box<T>> {
603    fn poll_result(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<T, Error>> {
604        let this = unsafe { self.get_unchecked_mut() };
605        this.as_mut().poll_result(cx)
606    }
607    fn poll_cancel(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
608        let this = unsafe { self.get_unchecked_mut() };
609
610        this.as_mut().poll_cancel(cx)
611    }
612}
613
614impl<E> AnyExecutorImpl for E
615where
616    E: Executor + 'static,
617{
618    fn spawn_boxed(
619        &self,
620        fut: Pin<Box<dyn Future<Output = ()> + Send>>,
621    ) -> Pin<Box<dyn Task<()> + Send>> {
622        let task = self.spawn(fut);
623        Box::pin(task)
624    }
625}
626
627#[cfg(feature = "std")]
628mod std_on {
629    use alloc::boxed::Box;
630
631    use crate::{
632        AnyExecutor, AnyExecutorTask, AnyLocalExecutor, AnyLocalExecutorTask, Executor,
633        LocalExecutor,
634    };
635
636    extern crate std;
637
638    use core::{cell::OnceCell, future::Future, panic::AssertUnwindSafe};
639    use std::sync::OnceLock;
640    std::thread_local! {
641        static LOCAL_EXECUTOR: OnceCell<AnyLocalExecutor> = const { OnceCell::new() };
642    }
643
644    /// Initialize the thread-local executor for spawning non-Send futures.
645    ///
646    /// This must be called before using [`spawn_local`]. The executor will be used
647    /// for all [`spawn_local`] calls on the current thread.
648    ///
649    /// # Panics
650    ///
651    /// Panics if a local executor has already been set for this thread.
652    ///
653    pub fn init_local_executor(executor: impl LocalExecutor + 'static) {
654        if try_init_local_executor(executor).is_err() {
655            panic!("Local executor already set for this thread");
656        }
657    }
658
659    /// Try to initialize the thread-local executor for spawning non-Send futures.
660    ///
661    /// This is a non-panicking version of [`init_local_executor`].
662    pub fn try_init_local_executor<E>(executor: E) -> Result<(), E>
663    where
664        E: LocalExecutor + 'static,
665    {
666        LOCAL_EXECUTOR.with(|cell| {
667            cell.set(AnyLocalExecutor::new(executor))
668                .map_err(|e| *e.downcast().unwrap())
669        })
670    }
671
672    static GLOBAL_EXECUTOR: OnceLock<AnyExecutor> = OnceLock::new();
673
674    /// Initialize the global executor for spawning Send futures.
675    ///
676    /// This must be called before using [`spawn`]. The executor will be used
677    /// for all [`spawn`] calls across all threads.
678    ///
679    /// # Panics
680    ///
681    /// Panics if a global executor has already been set.
682    ///
683    pub fn init_global_executor(executor: impl crate::Executor + 'static) {
684        if GLOBAL_EXECUTOR.set(AnyExecutor::new(executor)).is_err() {
685            panic!("Global executor already set");
686        }
687    }
688
689    /// Try to initialize the global executor for spawning Send futures.
690    ///
691    /// This is a non-panicking version of [`init_global_executor`].
692    pub fn try_init_global_executor<E>(executor: E) -> Result<(), E>
693    where
694        E: crate::Executor + 'static,
695    {
696        GLOBAL_EXECUTOR
697            .set(AnyExecutor::new(executor))
698            .map_err(|e| *e.downcast().unwrap())
699    }
700
701    /// Spawn a `Send` future on the global executor.
702    ///
703    /// The global executor must be initialized with [`init_global_executor`] before
704    /// calling this function.
705    ///
706    /// # Panics
707    ///
708    /// Panics if the global executor has not been set.
709    ///
710    pub fn spawn<Fut>(fut: Fut) -> AnyExecutorTask<Fut::Output>
711    where
712        Fut: Future<Output: Send> + Send + 'static,
713    {
714        let executor = GLOBAL_EXECUTOR.get().expect("Global executor not set");
715        executor.spawn(fut)
716    }
717
718    /// Spawn a future on the thread-local executor.
719    ///
720    /// The local executor must be initialized with [`init_local_executor`] before
721    /// calling this function. Unlike [`spawn`], this can handle futures that are
722    /// not `Send`.
723    ///
724    /// # Panics
725    ///
726    /// Panics if the local executor has not been set for this thread.
727    ///
728    pub fn spawn_local<Fut>(fut: Fut) -> AnyLocalExecutorTask<Fut::Output>
729    where
730        Fut: Future + 'static,
731    {
732        LOCAL_EXECUTOR.with(|cell| {
733            let executor = cell.get().expect("Local executor not set");
734            executor.spawn_local(fut)
735        })
736    }
737
738    #[allow(unused)]
739    pub(crate) fn catch_unwind<F, R>(f: F) -> Result<R, Box<dyn std::any::Any + Send>>
740    where
741        F: FnOnce() -> R,
742    {
743        std::panic::catch_unwind(AssertUnwindSafe(f))
744    }
745
746    /// A default executor that uses the global and local executors.
747    ///
748    /// This executor delegates to the global executor for `Send` futures
749    /// and the local executor for non-`Send` futures.
750    #[derive(Clone, Copy, Debug)]
751    pub struct DefaultExecutor;
752
753    impl Executor for DefaultExecutor {
754        type Task<T: Send + 'static> = AnyExecutorTask<T>;
755
756        fn spawn<Fut>(&self, fut: Fut) -> Self::Task<Fut::Output>
757        where
758            Fut: core::future::Future<Output: Send> + Send + 'static,
759        {
760            spawn(fut)
761        }
762    }
763
764    impl LocalExecutor for DefaultExecutor {
765        type Task<T: 'static> = AnyLocalExecutorTask<T>;
766
767        fn spawn_local<Fut>(&self, fut: Fut) -> Self::Task<Fut::Output>
768        where
769            Fut: core::future::Future + 'static,
770        {
771            spawn_local(fut)
772        }
773    }
774}
775
776#[cfg(feature = "std")]
777pub use std_on::*;