executor-core
A flexible task executor abstraction layer for Rust async runtimes.
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
ExecutorandLocalExecutortraits, using GAT to prevent unnecessary heap allocation and dynamic dispatch. -
Type Erasure:
AnyExecutorandAnyLocalExecutorfor 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:
use Executor;
pub async
Users call it with their runtime:
// tokio users
let runtime = new?;
let sum = parallel_sum.await;
// async-executor users
let executor = new;
let sum = parallel_sum.await;
Quick Start
Add to your Cargo.toml:
[]
= "0.6"
Basic Usage (Tokio)
use ;
use Runtime;
Using Different Executors
use Executor;
// Tokio executor
let runtime = new?;
let tokio_task = spawn;
let tokio_out = runtime.block_on;
// async-executor
let executor = new;
let async_task = spawn;
let async_out = block_on;
// Type-erased executor for runtime-agnostic storage
let any_executor = new;
let erased_task = any_executor.spawn;
Local Executors (Non-Send Futures)
use LocalExecutor;
use ;
let runtime = new.unwrap;
let handle = runtime.handle.clone;
let local_set = new;
// Run the local executor inside the Tokio runtime
let result = handle.block_on;
println!;
Error Handling & Background Work
use ;
use Runtime;
let runtime = new.unwrap;
let handle = runtime.handle.clone;
init_global_executor;
let task = spawn;
match handle.block_on
// Detach when you don't need the result
let fire_and_forget = spawn;
fire_and_forget.detach;
Runtime Support
Tokio
[]
= { = "0.6", = ["tokio"] }
use TokioTask;
use Executor;
// Use Tokio runtime directly
let runtime = new.unwrap;
let task: = spawn;
let output = runtime.block_on;
async-executor
[]
= { = "0.6", = ["async-executor"] }
use AsyncTask;
use Executor;
let executor = new;
let task: = spawn;
let output = block_on;
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 spawningSend + 'staticfuturesLocalExecutor: For spawning'staticfutures (not necessarilySend)
Both traits produce tasks that implement the Task trait, providing:
Futureimplementation for awaiting resultspoll_result()for explicit error handlingdetach()for background execution
Type-erased versions (AnyExecutor, AnyLocalExecutor) allow runtime executor selection.
No-std Support
Core functionality works in no-std environments:
[]
= { = "0.6", = 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 file for details.