A Rust implementation of C++ std::execution (P2300) based on the NVIDIA/stdexec reference implementation.
exec-rs provides lazy, composable asynchronous programming primitives with zero-cost abstractions using the sender/receiver model from C++ P2300.
use exec_rs::prelude::*;
// Compose async operations
let result = sync_wait(
just(21)
.then(|x| x * 2)
.let_value(|x| just(format!("Result: {}", x)))
).unwrap();
assert_eq!(result, "Result: 42");- Zero-cost abstractions: No heap allocation for basic operations
- Type-safe composition: Compile-time checked sender pipelines
- Structured concurrency: Scopes ensure proper lifetime management
- Cancellation support: Stop tokens propagate through pipelines
- Multiple schedulers: Thread pools, timed schedulers, inline execution
- Async I/O: mio-based networking with sender operations (P2762)
Add to your Cargo.toml:
[dependencies]
exec-rs = "0.1"use exec_rs::consumers::sync_wait;
use exec_rs::factories::just;
use exec_rs::traits::SenderExt;
fn main() {
// Simple value transformation
let result = sync_wait(
just(1)
.then(|x| x + 1)
.then(|x| x * 2)
).unwrap();
println!("Result: {}", result); // Result: 4
}use exec_rs::adaptors::on;
use exec_rs::consumers::sync_wait;
use exec_rs::factories::just;
use exec_rs::schedulers::StaticThreadPool;
use exec_rs::traits::SenderExt;
fn main() {
let pool = StaticThreadPool::new(4);
let scheduler = pool.get_scheduler();
let result = sync_wait(on(scheduler, just(42).then(|x| x * 2))).unwrap();
println!("Result: {}", result); // Result: 84
}- Source Files: 86 Rust files
- Unit Tests: 272 passing
- Examples: 14 examples across basics, algorithms, networking, and timed scheduling
Run examples with cargo run --example <name>:
| Example | Description |
|---|---|
hello_world |
Basic just, then, schedule, sync_wait |
let_value |
Dependent sender composition |
when_all |
Concurrent composition |
error_handling |
Error types, just_error, materialize |
thread_pool |
StaticThreadPool, on(), parallel work |
split |
Multi-shot sender with split |
counting_scope |
Structured concurrency |
| Example | Description |
|---|---|
parallel_for |
Bulk iteration patterns |
retry |
Retry patterns with senders |
async_inclusive_scan |
Parallel prefix sum algorithm |
| Example | Description |
|---|---|
tokio_echo |
Tokio-based echo server (baseline) |
exec_echo |
exec-rs sender-based echo server |
| Example | Description |
|---|---|
timed_scheduler |
Synchronous timed operations |
timed_scheduler_async |
Async timed operations |
| Phase | Description | Status |
|---|---|---|
| 1 | Core Traits + Stop Tokens | ✅ Complete |
| 2 | Basic Senders (Factories) | ✅ Complete |
| 3 | sync_wait + RunLoop | ✅ Complete |
| 4 | Value Transformation Adaptors | ✅ Complete |
| 5 | Sender Chaining (let_*) | ✅ Complete |
| 6 | Execution Context Switching | ✅ Complete |
| 7 | Concurrent Composition | ✅ Complete |
| 8 | Additional Adaptors | ✅ Complete |
| 9 | Structured Concurrency | ✅ Complete |
| 10 | Sequence Senders | |
| 11 | Type-Erased Senders | ✅ Complete |
| 12 | Advanced Schedulers | ✅ Complete |
| 13 | Coroutine Integration | ✅ Complete |
| 14 | Utilities | ✅ Complete |
| 15 | Future Bridge | ✅ Complete |
| 16 | Networking (P2762) | ✅ Complete |
just(value)- Immediate value completionjust_error(error)- Immediate error completionjust_stopped()- Immediate stopped completionjust_from(fn)- Lazy value from functionschedule(scheduler)- Schedule on execution contexttransfer_just(scheduler, value)- Transfer value to schedulerread_env(query)- Read from environment
then(fn)- Transform valueupon_error(fn)- Transform errorupon_stopped(fn)- Transform stoppedlet_value(fn)- Chain dependent senderlet_error(fn)- Handle error with senderlet_stopped(fn)- Handle stopped with senderon(scheduler, sender)- Run on schedulerstarts_on(scheduler, sender)- Start on schedulercontinues_on(scheduler)- Continue on schedulerwhen_all(s1, s2)- Wait for both senderswhen_any(s1, s2)- Race two senderssplit(sender)- Multi-shot senderensure_started(sender)- Eager executionbulk(shape, fn)- Parallel iterationmaterialize()- Completion to variantrepeat_n(n)- Repeat N timesrepeat_effect_until(pred)- Repeat with conditionfinally(cleanup)- Guaranteed cleanupfork_join2/3/4(fns...)- Fork and join
InlineScheduler- Synchronous inline executionRunLoop- Single-threaded event loopSingleThreadContext- Dedicated thread executorStaticThreadPool- Multi-threaded work-stealing poolTrampolineScheduler- Recursion-safe schedulerManualScheduler- Manual advance for testingTimedThreadScheduler- Timer-based scheduling
AsyncScope- Tracks nested sender lifetimesCountingScope- Reference-counting scopenest(sender)- Track sender in scope
Following the P2762 async I/O proposal:
IoContext- Event loop with mioTcpListener/TcpStream- TCP socketsasync_accept()- Accept connectionsasync_receive()/async_send()- I/O operations
Single-threaded echo server comparison:
| Server | Light (50 conn) | Medium (200) | Heavy (500) | Large (4KB) |
|---|---|---|---|---|
| Tokio | 104,601 req/s | 114,478 | 107,333 | 85,745 |
| exec-rs | 114,723 req/s | 130,694 | 135,157 | 100,321 |
exec-rs outperforms Tokio by 10-26% in single-threaded mode.
- Nested
when_all: TheWhenAllReceivertypes don't implementClone, preventing nestedwhen_allcalls. Use sequential pairs instead. - Sequence senders:
mergeandtransform_eachare partially implemented.
- P2300R10 std::execution
- P2762 Sender/Receiver Async I/O
- NVIDIA/stdexec - Reference implementation
Apache-2.0