#async-concurrency #task #concurrency

native-executor

Platform-native async task executor

10 releases (5 breaking)

Uses new Rust 2024

0.7.0 Dec 5, 2025
0.6.0 Oct 15, 2025
0.5.3 Oct 5, 2025
0.5.1 Sep 8, 2025
0.1.0 Aug 31, 2025

#417 in Asynchronous


Used in 2 crates

MIT license

49KB
1K SLoC

Native Executor

Crates.io MIT licensed docs.rs

Platform-native async task executor that leverages OS event loops (GCD, GDK) for optimal performance.

Features

  • Platform-native scheduling: Direct GCD integration on Apple platforms
  • Structured concurrency: Tasks are tied to their handles; dropping an un-awaited handle cancels the task unless it was detached
  • Priority-aware execution: Background vs default task prioritization
  • Thread-local safety: Non-Send future execution with compile-time guarantees
  • Mailbox-based messaging: Share state via serialized cross-thread queues
  • Zero-cost abstractions: Direct OS API usage, no additional runtime

Quick Start

use native_executor::{spawn_local, timer::Timer};
use std::time::Duration;

// Spawn a task with default priority
let handle = spawn_local(async {
    println!("Starting async task");

    Timer::after(Duration::from_secs(1)).await;

    println!("Task completed after 1 second");
});
// Keep the task alive: awaiting is structured; detach for fire-and-forget.
handle.detach();

// Keep the main thread alive to allow tasks to complete
std::thread::sleep(Duration::from_secs(2));

Structured Concurrency

All spawn* functions return AsyncTask handles that own the task lifecycle. Dropping the handle without calling .await or .detach() cancels the task immediately. Awaiting the handle gives structured shutdown and propagates panics; detach() opts out and lets the task run to completion in the background when you truly need fire-and-forget behavior.

Core Components

Task Spawning

use native_executor::{spawn, spawn_local, spawn_main, spawn_with_priority, Priority};

spawn(async { /* default priority */ });
spawn_local(async { /* non-Send, main thread */ });
spawn_main(async { /* Send, main thread */ });
spawn_with_priority(async { /* background work */ }, Priority::Background);

Timers

use native_executor::timer::{Timer, sleep};
use std::time::Duration;

async {
    Timer::after(Duration::from_millis(100)).await;  // Precise timing
    Timer::after_secs(2).await;                      // Convenience method
    sleep(1).await;                                  // Simple sleep
};

Mailbox Messaging

use native_executor::mailbox::Mailbox;
use std::{cell::RefCell, collections::HashMap};

let mailbox = Mailbox::main(RefCell::new(HashMap::<String, i32>::new()));

// Send fire-and-forget updates
mailbox.handle(|map| {
    map.borrow_mut().insert("key".to_string(), 42);
});

// Cross-thread with main-thread execution
let main_val = MainValue::new(String::from("UI data"));
async {
    let len = main_val.handle(|s| s.len()).await;
};

Platform Support

Current: Apple platforms (macOS, iOS, tvOS, watchOS) via Grand Central Dispatch, Android (native worker queues)
Planned: Linux (GDK)

Unsupported platforms fail at compile-time with clear error messages.

Polyfill Feature

The optional polyfill feature (enabled by default) provides a simulated executor for targets without a native implementation. Its behavior is as follows:

  • On Apple, Android, and wasm32 targets the feature is a no-op – the native executors and timers always take precedence.
  • On other targets the crate will not build unless the polyfill feature is enabled. Disabling it makes the lack of a native executor a hard error.
  • The polyfill spins up its own worker threads and exposes a synthetic "main thread". Call native_executor::polyfill::start_main_executor() on a dedicated thread before using spawn_main or spawn_local.
  • Because this main thread is not provided by the OS event loop, code that depends on true main-thread semantics (UI frameworks, platform APIs, etc.) may behave differently. The feature exists only as a portability fallback.

Example setup for unsupported targets:

#[cfg(all(feature = "polyfill", not(any(target_vendor = "apple", target_arch = "wasm32", target_os = "android"))))]
std::thread::spawn(|| native_executor::polyfill::start_main_executor());

Examples

cargo run --example simple_task    # Basic spawning
cargo run --example priority       # Priority control
cargo run --example timers         # High-precision timing
cargo run --example main_thread     # Main thread execution
cargo run --example local_value     # Thread-safe containers

License

This project is licensed under the MIT License.

Dependencies

~0.5–16MB
~188K SLoC