executor_core/
lib.rs

1#![no_std]
2//! A flexible task executor abstraction layer for Rust async runtimes.
3//!
4//! This crate provides a unified interface for spawning and managing async tasks across
5//! different executor backends. It supports both global and local executors, with built-in
6//! implementations for popular runtimes like [`async-executor`] and [`tokio`].
7
8//! # Quick Start
9//!
10//! ```rust
11//! use executor_core::spawn;
12//!
13//! // Spawn a task on the global executor
14//! let task = spawn(async {
15//!     println!("Hello from async task!");
16//!     42
17//! });
18//!
19//! // Await the result
20//! let result = task.await;
21//! assert_eq!(result, 42);
22//! ```
23//!
24//! # Error Handling
25//!
26//! Tasks can be awaited directly (panics propagate) or handled explicitly:
27//!
28//! ```rust
29//! use executor_core::{spawn, Error};
30//! let task = spawn(async {
31//!     panic!("Something went wrong");
32//!     42
33//! });
34//!
35//! // Option 1: Direct await (panics propagate)
36//! let result = task.await;
37//!
38//! // Option 2: Explicit error handling
39//! match task.result().await {
40//!     Ok(value) => println!("Success: {}", value),
41//!     Err(Error::Panicked(msg)) => println!("Task panicked: {}", msg),
42//!     Err(Error::Cancelled) => println!("Task was cancelled"),
43//! }
44//!
45//! ```
46//!
47//! # Thread Safety
48//!
49//! The crate provides separate traits for thread-safe and thread-local execution:
50//!
51//! - [`Executor`] + [`Task`]: For `Send` futures that can move between threads
52//! - [`LocalExecutor`] + [`LocalTask`]: For non-`Send` futures bound to one thread
53//!
54//! ```rust
55//! use executor_core::{Executor, LocalExecutor};
56//! use std::rc::Rc;
57//!
58//! // ✅ Send futures work with Executor
59//! let executor = async_executor::Executor::new();
60//! let task = executor.spawn(async { "Hello".to_string() });
61//!
62//! // ✅ Non-Send futures work with LocalExecutor
63//! let local_executor = async_executor::LocalExecutor::new();
64//! let local_task = local_executor.spawn(async {
65//!     let data = Rc::new(42); // Rc is not Send
66//!     *data
67//! });
68//! ```
69//!
70//! # Feature Flags
71//!
72//! - `default-async-executor` (default) - Use [`async-executor`] as the global executor
73//! - `default-tokio` - Use [`tokio`] as the global executor
74//! - `async-executor` - Enable [`async-executor`] backend support
75//! - `tokio` - Enable [`tokio`] backend support
76//! - `std` - Enable standard library support (auto-enabled by executor backends)
77//!
78//! [`async-executor`]: https://docs.rs/async-executor
79//! [`tokio`]: https://docs.rs/tokio
80
81extern crate alloc;
82use alloc::borrow::Cow;
83use core::future::Future;
84
85#[cfg(feature = "std")]
86extern crate std;
87
88#[cfg(feature = "async-executor")]
89mod async_executor;
90#[cfg(feature = "tokio")]
91mod tokio;
92
93#[cfg(feature = "default-async-executor")]
94#[doc(hidden)]
95pub use async_executor::DefaultExecutor;
96
97#[cfg(feature = "default-async-executor")]
98#[doc(hidden)]
99pub use async_executor::DefaultLocalExecutor;
100
101#[cfg(all(feature = "default-async-executor", feature = "default-tokio"))]
102compile_error!(
103    "Cannot enable both default-async-executor and default-tokio features simultaneously"
104);
105
106/// A trait for executor implementations that can spawn thread-safe futures.
107///
108/// This trait represents executors capable of running concurrent tasks that are `Send`,
109/// meaning they can be safely moved between threads. Implementors provide the core
110/// functionality for task spawning in a multi-threaded context.
111pub trait Executor {
112    /// Spawns a thread-safe future on this executor.
113    fn spawn<T: Send + 'static>(
114        &self,
115        fut: impl Future<Output = T> + Send + 'static,
116    ) -> impl Task<Output = T>;
117}
118
119/// A trait for executor implementations that can spawn futures on the current thread.
120///
121/// This trait represents executors that operate within a single thread context,
122/// allowing them to work with futures that are not `Send`. This is essential for
123/// working with non-thread-safe types like [Rc](alloc::rc::Rc), [RefCell](core::cell::RefCell), or thread-local storage.
124pub trait LocalExecutor {
125    /// Spawns a future on this local executor.
126    fn spawn<T: 'static>(
127        &self,
128        fut: impl Future<Output = T> + 'static,
129    ) -> impl LocalTask<Output = T>;
130}
131
132/// A handle to a spawned task that can be safely moved between threads.
133///
134/// This trait extends [`Future`], which means it can be awaited to get the result directly.
135///
136/// # Panics
137/// If the task is cancelled or panics during execution, the attempt to await the task will panic.
138/// If you need to handle these cases gracefully, use the [`result`] method instead.
139pub trait Task: Future + 'static + Send {
140    /// Returns the task result or error without panicking.
141    ///
142    /// Unlike directly awaiting the task, this method returns a [`Result`] that
143    /// allows you to handle task panics and cancellation gracefully.
144    ///
145    /// # Returns
146    ///
147    /// - `Ok(T)` - Task completed successfully
148    /// - `Err(Error::Panicked(_))` - Task panicked during execution
149    /// - `Err(Error::Cancelled)` - Task was cancelled
150    ///
151    /// # Examples
152    ///
153    /// ```rust
154    /// use executor_core::{spawn, Error};
155    ///
156    /// let task = spawn(async { 42 });
157    ///
158    /// match task.result().await {
159    ///     Ok(value) => println!("Result: {}", value),
160    ///     Err(Error::Panicked(msg)) => eprintln!("Panic: {}", msg),
161    ///     Err(Error::Cancelled) => println!("Cancelled"),
162    /// }
163    /// ```
164    fn result(self) -> impl Future<Output = Result<Self::Output, Error>> + Send;
165
166    /// Cancels the task, preventing further execution.
167    ///
168    /// After calling this method, the task will stop executing as soon as possible.
169    /// Any attempt to await the task or get its result will return [`Error::Cancelled`].
170    ///
171    /// # Notes
172    ///
173    /// - Cancellation is cooperative and may not be immediate
174    /// - Already completed tasks cannot be cancelled
175    ///
176    /// # Examples
177    ///
178    /// ```rust
179    /// use executor_core::spawn;
180    /// use std::time::Duration;
181    ///
182    /// let task = spawn(async {
183    ///     tokio::time::sleep(Duration::from_secs(10)).await;
184    ///     "Done"
185    /// });
186    ///
187    /// // Cancel the long-running task
188    /// task.cancel();
189    /// ```
190    fn cancel(self);
191}
192
193/// A handle to a spawned task that may not be `Send`.
194///
195/// Similar to [`Task`], but for futures that must execute on the same thread
196/// they were spawned on. This is useful for working with non-thread-safe types.
197pub trait LocalTask: Future + 'static {
198    /// Returns the task result or error without panicking.
199    ///
200    /// Similar to [`Task::result`], but for local tasks that are not `Send`.
201    fn result(self) -> impl Future<Output = Result<Self::Output, Error>>;
202
203    /// Cancels the local task, preventing further execution.
204    ///
205    /// Similar to [`Task::cancel`], but for local tasks.
206    fn cancel(self);
207}
208
209/// Errors that can occur during task execution.
210///
211/// This enum represents the different ways a task can fail, allowing for
212/// proper error handling and recovery strategies.
213#[derive(Debug, Clone, PartialEq, Eq)]
214pub enum Error {
215    /// The task panicked during execution.
216    ///
217    /// Contains the panic message if available, or a generic message if the
218    /// panic payload couldn't be converted to a string.
219    Panicked(Cow<'static, str>),
220
221    /// The task was cancelled before completion.
222    ///
223    /// This occurs when [`Task::cancel`] or [`LocalTask::cancel`] is called
224    /// on a running task.
225    Cancelled,
226}
227
228impl core::fmt::Display for Error {
229    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
230        match self {
231            Error::Panicked(msg) => write!(f, "Task panicked: {msg}"),
232            Error::Cancelled => write!(f, "Task was cancelled"),
233        }
234    }
235}
236
237#[cfg(feature = "std")]
238impl std::error::Error for Error {}
239
240#[cfg(feature = "std")]
241static GLOBAL_EXECUTOR: std::sync::LazyLock<DefaultExecutor> =
242    std::sync::LazyLock::new(DefaultExecutor::default);
243
244/// Spawns a new task on the global executor.
245///
246/// This is a convenience function that spawns a task using the default global executor
247/// instance. The spawned task must be `Send + 'static`, allowing it to be moved between
248/// threads.
249///
250/// # Global Executor
251///
252/// The global executor is determined by feature flags:
253/// - With `default-async-executor` (default): Uses [`async_executor::Executor`]
254/// - With `default-tokio`: Uses [`tokio::runtime::Runtime`]
255#[cfg(feature = "std")]
256pub fn spawn<T: Send + 'static>(
257    fut: impl Future<Output = T> + Send + 'static,
258) -> impl Task<Output = T> {
259    use core::ops::Deref;
260    Executor::spawn(GLOBAL_EXECUTOR.deref(), fut)
261}