| Crates.io | executor-core |
| lib.rs | executor-core |
| version | 0.7.1 |
| created_at | 2025-06-09 15:29:16.595913+00 |
| updated_at | 2025-12-09 07:07:52.346506+00 |
| description | A zero-cost task executor abstraction layer for Rust async runtimes |
| homepage | https://github.com/lexoliu/executor-core |
| repository | https://github.com/lexoliu/executor-core |
| max_upload_size | |
| id | 1706050 |
| size | 86,036 |
A flexible task executor abstraction layer for Rust async runtimes.
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.
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:
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
Instead of hard-coding tokio::spawn, accept an executor parameter:
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:
// 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;
Add to your Cargo.toml:
[dependencies]
executor-core = "0.6"
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);
}
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 });
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);
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();
[dependencies]
executor-core = { version = "0.6", features = ["tokio"] }
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);
[dependencies]
executor-core = { version = "0.6", features = ["async-executor"] }
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));
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 featuresThe crate is built around two main traits:
Executor: For spawning Send + 'static futuresLocalExecutor: For spawning 'static futures (not necessarily Send)Both traits produce tasks that implement the Task trait, providing:
Future implementation for awaiting resultspoll_result() for explicit error handlingdetach() for background executionType-erased versions (AnyExecutor, AnyLocalExecutor) allow runtime executor selection.
Core functionality works in no-std environments:
[dependencies]
executor-core = { version = "0.6", default-features = false }
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.