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}