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}