executor-core 0.7.1

A zero-cost task executor abstraction layer for Rust async runtimes
Documentation
# executor-core

A flexible task executor abstraction layer for Rust async runtimes.

[![Crates.io](https://img.shields.io/crates/v/executor-core.svg)](https://crates.io/crates/executor-core)
[![Documentation](https://docs.rs/executor-core/badge.svg)](https://docs.rs/executor-core)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Coverage](https://img.shields.io/codecov/c/github/lexoliu/executor-core?logo=codecov)](https://app.codecov.io/gh/lexoliu/executor-core)

## Overview

`executor-core` provides unified traits and type-erased wrappers for different async executors in Rust. It allows you to write code that's agnostic to the underlying executor implementation, whether you're using Tokio, async-executor, or custom executors.

Write async libraries without choosing a runtime. Your users should decide whether to use tokio, async-executor, or any other runtime. Not you.

## Features

- **Zero-cost Executor Abstraction**: Unified `Executor` and `LocalExecutor` traits, using GAT to prevent unnecessary heap allocation and dynamic dispatch.

- **Type Erasure**: `AnyExecutor` and `AnyLocalExecutor` for runtime flexibility
- **Multiple Runtime Support**:
  - **Tokio**: Integration with Tokio runtime and LocalSet
  - **async-executor**: Support for async-executor crate
- **Task Management**: Rich task API with cancellation and error handling
- **No-std Compatible**: Core functionality works in no-std environments
- **Panic Safety**: Proper panic handling and propagation

## How It Works

Instead of hard-coding `tokio::spawn`, accept an executor parameter:

```rust
use executor_core::Executor;

pub async fn parallel_sum<E: Executor>(
    executor: &E,
    numbers: Vec<i32>
) -> i32 {
    let (left, right) = numbers.split_at(numbers.len() / 2);

    let left_sum = executor.spawn(async move {
        left.iter().sum::<i32>()
    });

    let right_sum = executor.spawn(async move {
        right.iter().sum::<i32>()
    });

    left_sum.await + right_sum.await
}
```

Users call it with their runtime:

```rust
// tokio users
let runtime = tokio::runtime::Runtime::new()?;
let sum = parallel_sum(&runtime, vec![1, 2, 3, 4]).await;

// async-executor users
let executor = async_executor::Executor::new();
let sum = parallel_sum(&executor, vec![1, 2, 3, 4]).await;
```

## Quick Start

Add to your `Cargo.toml`:

```toml
[dependencies]
executor-core = "0.6"
```

### Basic Usage (Tokio)

```rust
use executor_core::{Executor, init_global_executor, spawn};
use executor_core::tokio::Runtime;

fn main() {
    // Initialize the global executor
    let runtime = Runtime::new().unwrap();
    let handle = runtime.handle().clone();
    init_global_executor(runtime);

    // Spawn a task
    let task = spawn(async {
        println!("Hello from spawned task!");
        42
    });

    let result = handle.block_on(task);
    println!("Task result: {}", result);
}
```

### Using Different Executors

```rust
use executor_core::Executor;

// Tokio executor
let runtime = tokio::runtime::Runtime::new()?;
let tokio_task = Executor::spawn(&runtime, async { "tokio result" });
let tokio_out = runtime.block_on(tokio_task);

// async-executor
let executor = async_executor::Executor::new();
let async_task = Executor::spawn(&executor, async { "async-executor result" });
let async_out = futures_lite::future::block_on(executor.run(async_task));

// Type-erased executor for runtime-agnostic storage
let any_executor = executor_core::AnyExecutor::new(runtime);
let erased_task = any_executor.spawn(async { 99u8 });
```

### Local Executors (Non-Send Futures)

```rust
use executor_core::LocalExecutor;
use executor_core::tokio::{LocalSet, Runtime};

let runtime = Runtime::new().unwrap();
let handle = runtime.handle().clone();
let local_set = LocalSet::new();

// Run the local executor inside the Tokio runtime
let result = handle.block_on(local_set.run_until(async {
    let task = LocalExecutor::spawn_local(&local_set, async {
        // This future doesn't need to be Send
        let local_data = std::rc::Rc::new(42);
        *local_data
    });

    task.await
}));
println!("Local task result: {}", result);
```

### Error Handling & Background Work

```rust
use executor_core::{Executor, init_global_executor, spawn, Task};
use executor_core::tokio::Runtime;

let runtime = Runtime::new().unwrap();
let handle = runtime.handle().clone();
init_global_executor(runtime);

let task = spawn(async {
    panic!("Something went wrong!");
});

match handle.block_on(task.result()) {
    Ok(value) => println!("Task completed: {}", value),
    Err(error) => println!("Task failed: {:?}", error),
}

// Detach when you don't need the result
let fire_and_forget = spawn(async { 1 + 1 });
fire_and_forget.detach();
```

## Runtime Support

### Tokio

```toml
[dependencies]
executor-core = { version = "0.6", features = ["tokio"] }
```

```rust
use executor_core::tokio::TokioTask;
use executor_core::Executor;

// Use Tokio runtime directly
let runtime = tokio::runtime::Runtime::new().unwrap();
let task: TokioTask<_> = Executor::spawn(&runtime, async { "direct runtime usage" });
let output = runtime.block_on(task);
```

### async-executor

```toml
[dependencies]
executor-core = { version = "0.6", features = ["async-executor"] }
```

```rust
use executor_core::AsyncTask;
use executor_core::Executor;

let executor = async_executor::Executor::new();
let task: AsyncTask<_> = Executor::spawn(&executor, async { "async-executor" });
let output = futures_lite::future::block_on(executor.run(task));
```

## Feature Flags

- `std` - Enable std functionality (enabled by default)
- `tokio` - Tokio runtime support (enabled by default)
- `async-executor` - async-executor support (enabled by default)
- `full` - Enable all features

## Architecture

The crate is built around two main traits:

- **`Executor`**: For spawning `Send + 'static` futures
- **`LocalExecutor`**: For spawning `'static` futures (not necessarily `Send`)

Both traits produce tasks that implement the `Task` trait, providing:

- `Future` implementation for awaiting results
- `poll_result()` for explicit error handling
- `detach()` for background execution

Type-erased versions (`AnyExecutor`, `AnyLocalExecutor`) allow runtime executor selection.

## No-std Support

Core functionality works in `no-std` environments:

```toml
[dependencies]
executor-core = { version = "0.6", default-features = false }
```

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.