Skip to main content

native_executor/
lib.rs

1//! Platform-native async executor that bridges directly to OS event loops.
2//!
3//! Tasks use structured concurrency semantics: every `spawn*` call returns an
4//! [`AsyncTask`] handle, and dropping that handle cancels the task unless you
5//! awaited it or called [`AsyncTask::detach`] to explicitly opt into
6//! fire-and-forget execution.
7use std::{future::Future, time::Duration};
8
9#[cfg(target_vendor = "apple")]
10mod apple;
11use executor_core::{Executor, LocalExecutor, async_task::AsyncTask};
12
13#[cfg(target_os = "android")]
14pub mod android;
15#[cfg(target_arch = "wasm32")]
16mod web;
17
18#[cfg(any(
19    all(feature = "polyfill", not(target_arch = "wasm32")),
20    target_os = "android"
21))]
22pub mod polyfill;
23
24#[cfg(all(
25    not(feature = "polyfill"),
26    not(target_vendor = "apple"),
27    not(target_os = "android"),
28    not(target_arch = "wasm32")
29))]
30compile_error!(
31    "native-executor has no backend for this target; enable the `polyfill` feature \
32     to build on unsupported platforms."
33);
34
35/// Task execution priority levels for controlling scheduler behavior.
36///
37/// These priority levels map to platform-native scheduling priorities,
38/// allowing fine-grained control over task execution order and resource allocation.
39///
40/// # Platform notes
41/// - Apple (macOS/iOS/Catalyst) and wasm/web backends are ready to use with no extra setup.
42/// - Android **requires** calling [`register_android_main_thread`](crate::register_android_main_thread)
43///   on the real UI thread before any `spawn_main`/`spawn_main_local` usage, so the executor can
44///   dispatch tasks back to the platform main looper.
45/// - Polyfill backend (enabled via the `polyfill` feature on unsupported targets) needs you to
46///   create a dedicated thread and call [`polyfill::executor::start_main_executor`] there to
47///   simulate a main thread before using `spawn_main`/`spawn_local`.
48///
49/// # Choosing an executor
50///
51/// [`NativeExecutor`] is the global, work-stealing executor and has no
52/// main-thread affinity, so it is always available.
53///
54/// [`NativeMainExecutor`] dispatches to the platform main thread and only exists
55/// where such a thread does, which is why it is constructed through an
56/// [`Option`] rather than failing at the first spawn.
57///
58/// **If your program owns an event loop — winit, GTK/glib, a frame pump — do not
59/// use [`NativeMainExecutor`].** Implement [`LocalExecutor`] over that loop so
60/// tasks run on the thread that owns your windows and graphics resources.
61/// [`polyfill::executor::start_main_executor`] would otherwise declare an
62/// unrelated thread "main" and dispatch your UI work to the wrong one.
63#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
64#[non_exhaustive]
65pub enum Priority {
66    /// Standard priority level for most application tasks.
67    ///
68    /// This is the default priority that provides balanced execution
69    /// suitable for general-purpose async operations.
70    #[default]
71    Default,
72    /// Lower priority for background tasks and non-critical operations.
73    ///
74    /// Background tasks yield CPU time to higher-priority tasks and are
75    /// ideal for operations like cleanup, logging, or data processing
76    /// that don't require immediate completion.
77    Background,
78    /// Higher priority for user-initiated tasks that require prompt execution.
79    /// This priority is suitable for tasks that directly impact user experience,
80    /// such as responding to user input or updating the UI.
81    UserInitiated,
82    /// Highest priority for tasks that require immediate attention to maintain
83    /// application responsiveness.
84    /// This priority should be reserved for critical operations that must
85    /// complete as soon as possible, such as rendering UI updates or handling
86    /// real-time data.
87    UserInteractive,
88    /// Lowest priority for tasks that can be deferred until the system is idle.
89    /// This priority is suitable for maintenance tasks, prefetching data,
90    /// or other operations that do not need to run immediately and can wait
91    /// until the system is less busy.
92    Utility,
93}
94
95trait PlatformExecutor {
96    type Timer: Future<Output = ()>;
97    fn with_priority(priority: Priority) -> Self;
98    fn sleep(duration: Duration) -> Self::Timer;
99    fn spawn<Fut>(&self, fut: Fut) -> AsyncTask<Fut::Output>
100    where
101        Fut: Future<Output: Send> + Send + 'static;
102    fn spawn_main<Fut>(&self, fut: Fut) -> AsyncTask<Fut::Output>
103    where
104        Fut: Future<Output: Send> + Send + 'static;
105    fn spawn_main_local<Fut>(&self, fut: Fut) -> AsyncTask<Fut::Output>
106    where
107        Fut: Future + 'static;
108}
109
110#[cfg(target_vendor = "apple")]
111type NativeExecutorInner = apple::AppleExecutor;
112
113#[cfg(target_os = "android")]
114type NativeExecutorInner = android::AndroidExecutor;
115
116#[cfg(target_arch = "wasm32")]
117type NativeExecutorInner = web::WebExecutor;
118
119#[cfg(all(
120    feature = "polyfill",
121    not(any(target_vendor = "apple", target_os = "android", target_arch = "wasm32"))
122))]
123type NativeExecutorInner = polyfill::executor::PolyfillExecutor;
124
125#[cfg(target_os = "android")]
126pub use android::register_android_main_thread;
127
128#[derive(Debug)]
129pub struct NativeExecutor(NativeExecutorInner);
130
131impl Default for NativeExecutor {
132    fn default() -> Self {
133        Self::new()
134    }
135}
136
137impl NativeExecutor {
138    #[must_use]
139    pub fn new() -> Self {
140        Self::with_priority(Priority::default())
141    }
142
143    #[must_use]
144    pub fn with_priority(priority: Priority) -> Self {
145        Self(<NativeExecutorInner as PlatformExecutor>::with_priority(
146            priority,
147        ))
148    }
149
150    pub fn spawn<Fut>(&self, fut: Fut) -> AsyncTask<Fut::Output>
151    where
152        Fut: Future<Output: Send> + Send + 'static,
153    {
154        <NativeExecutorInner as PlatformExecutor>::spawn(&self.0, fut)
155    }
156}
157
158/// Reports whether this target has an established main thread to dispatch to.
159///
160/// Apple and web targets always do. Android needs
161/// [`register_android_main_thread`], and the polyfill needs
162/// [`polyfill::executor::start_main_executor`].
163#[must_use]
164#[allow(
165    clippy::missing_const_for_fn,
166    reason = "only the apple/web arm is a constant; the others read a OnceLock"
167)]
168pub fn main_thread_available() -> bool {
169    #[cfg(any(target_vendor = "apple", target_arch = "wasm32"))]
170    {
171        true
172    }
173    #[cfg(target_os = "android")]
174    {
175        android::is_main_thread_registered()
176    }
177    #[cfg(all(
178        feature = "polyfill",
179        not(target_vendor = "apple"),
180        not(target_os = "android"),
181        not(target_arch = "wasm32")
182    ))]
183    {
184        polyfill::is_main_thread_registered()
185    }
186}
187
188/// Dispatches work to the platform main thread.
189///
190/// This is deliberately a separate type from [`NativeExecutor`]: only targets
191/// with an established main thread can produce one, so handing a main-thread
192/// executor to an API that needs one is checked when you construct it rather
193/// than when the first task is spawned.
194///
195/// **If you own an event loop, do not use this type.** Implement
196/// [`LocalExecutor`] over your own loop instead — winit, GTK/glib, or a frame
197/// pump — so tasks run on the thread that owns your windows. This type is for
198/// hosts that have no loop of their own.
199#[derive(Debug)]
200pub struct NativeMainExecutor(NativeExecutorInner);
201
202impl NativeMainExecutor {
203    /// Returns `None` when no main thread has been established.
204    ///
205    /// That is never the case on Apple and web targets. On Android it means
206    /// [`register_android_main_thread`] has not run yet; under the polyfill it
207    /// means no thread is running
208    /// [`polyfill::executor::start_main_executor`].
209    #[must_use]
210    pub fn new() -> Option<Self> {
211        Self::with_priority(Priority::default())
212    }
213
214    /// Like [`NativeMainExecutor::new`], with an explicit priority.
215    #[must_use]
216    pub fn with_priority(priority: Priority) -> Option<Self> {
217        main_thread_available().then(|| {
218            Self(<NativeExecutorInner as PlatformExecutor>::with_priority(
219                priority,
220            ))
221        })
222    }
223
224    pub fn spawn_main<Fut>(&self, fut: Fut) -> AsyncTask<Fut::Output>
225    where
226        Fut: Future<Output: Send> + Send + 'static,
227    {
228        <NativeExecutorInner as PlatformExecutor>::spawn_main(&self.0, fut)
229    }
230
231    /// # Panics
232    ///
233    /// Under the polyfill, panics unless called from the thread running
234    /// [`polyfill::executor::start_main_executor`]; that backend runs the task
235    /// inline rather than dispatching it.
236    pub fn spawn_main_local<Fut>(&self, fut: Fut) -> <Self as LocalExecutor>::Task<Fut::Output>
237    where
238        Fut: Future + 'static,
239    {
240        <NativeExecutorInner as PlatformExecutor>::spawn_main_local(&self.0, fut)
241    }
242}
243
244/// A timer that completes after a specified duration.
245#[derive(Debug)]
246pub struct NativeTimer(<NativeExecutorInner as PlatformExecutor>::Timer);
247
248impl NativeTimer {
249    #[must_use]
250    pub fn after(duration: Duration) -> Self {
251        Self(<NativeExecutorInner as PlatformExecutor>::sleep(duration))
252    }
253}
254
255impl Future for NativeTimer {
256    type Output = ();
257    fn poll(
258        mut self: std::pin::Pin<&mut Self>,
259        cx: &mut std::task::Context<'_>,
260    ) -> std::task::Poll<Self::Output> {
261        std::pin::pin!(&mut self.0).poll(cx)
262    }
263}
264
265impl Executor for NativeExecutor {
266    type Task<T: Send + 'static> = AsyncTask<T>;
267    fn spawn<Fut>(&self, fut: Fut) -> Self::Task<Fut::Output>
268    where
269        Fut: Future<Output: Send> + Send + 'static,
270    {
271        <NativeExecutorInner as PlatformExecutor>::spawn(&self.0, fut)
272    }
273}
274
275/// # Panics
276///
277/// Under the polyfill, panics if not called from the registered main thread.
278impl LocalExecutor for NativeMainExecutor {
279    type Task<T: 'static> = AsyncTask<T>;
280    fn spawn_local<Fut>(&self, fut: Fut) -> Self::Task<Fut::Output>
281    where
282        Fut: Future + 'static,
283    {
284        <NativeExecutorInner as PlatformExecutor>::spawn_main_local(&self.0, fut)
285    }
286}
287
288#[must_use]
289pub fn sleep(duration: Duration) -> NativeTimer {
290    NativeTimer::after(duration)
291}