# nexus-async-rt
Single-threaded async runtime for latency-sensitive systems. Built on mio.
Not a tokio replacement — a purpose-built alternative for single-threaded
event loops where predictable latency matters more than multi-threaded
throughput.
## When to use this vs tokio
**Use nexus-async-rt when:**
- Single-threaded event loop (one core, no work-stealing)
- Predictable tail latency (no scheduler jitter)
- Zero-alloc task spawning on the hot path (slab allocation)
- Futures are `!Send` — and that's fine
- You're building on the nexus ecosystem (nexus-net, nexus-rt)
**Use tokio when:**
- Multi-threaded execution or work-stealing
- tokio ecosystem (tower, hyper, tonic, etc.)
- Futures need to be `Send` across threads
- Broad community support matters
Both can coexist in the same process. Use nexus-async-rt for the
latency-critical event loop and tokio for everything else.
## Quick start
```rust
use nexus_async_rt::*;
use nexus_rt::WorldBuilder;
let mut world = WorldBuilder::new().build();
let mut rt = Runtime::new(&mut world);
rt.block_on(async {
let handle = spawn_boxed(async { 42 });
let result = handle.await;
assert_eq!(result, 42);
});
```
## What you get
### Task spawning
Two strategies, same API:
```rust
// Box-allocated — default, no setup needed
let handle = spawn_boxed(async { compute() });
// Slab-allocated — pre-allocated, zero-alloc hot path
let handle = spawn_slab(async { compute() });
```
Both return `JoinHandle<T>` — await for the result, drop to detach,
or call `abort()` to cancel (consumes the handle).
### Slab allocation (zero-alloc spawn)
For hot-path tasks where allocation jitter is unacceptable:
```rust
// SAFETY: single-threaded runtime owns the slab.
let slab = unsafe { Slab::<256>::with_chunk_capacity(64) };
let mut rt = Runtime::builder(&mut world)
.slab_unbounded(slab)
.build();
rt.block_on(async {
// Pre-allocated — no Box, no allocator, zero syscalls
let handle = spawn_slab(async { fast_path() });
handle.await
});
```
Or claim a slot first, spawn later:
```rust
if let Some(claim) = try_claim_slab() {
let handle = claim.spawn(async { work() });
// ...
}
```
### Timers
```rust
use std::time::Duration;
// Sleep
sleep(Duration::from_millis(100)).await;
// Timeout
let result = timeout(Duration::from_secs(5), some_future).await;
// Interval
let mut tick = interval(Duration::from_millis(10));
loop {
tick.tick().await;
poll_market_data();
}
```
### I/O (mio-based)
```rust
use nexus_async_rt::{TcpStream, TcpListener, io};
// Client
let stream = TcpStream::connect(addr, io())?;
// Server
let listener = TcpListener::bind(addr, io())?;
let (stream, peer) = listener.accept().await?;
```
### Channels
Three flavors for different use cases:
```rust
use nexus_async_rt::channel;
// Local MPSC — !Send, zero atomics, single-threaded
let (tx, rx) = channel::local::channel(64);
// Cross-thread MPSC — Sender: Clone + Send
let (tx, rx) = channel::mpsc::channel(64);
// Cross-thread SPSC — fastest cross-thread path
let (tx, rx) = channel::spsc::channel(64);
```
### World access
Access nexus-rt `World` resources from async tasks:
```rust
with_world(|world| {
let config = world.resource::<Config>();
// ...
});
```
### Graceful shutdown
```rust
rt.block_on(async {
// ... spawn tasks ...
shutdown_signal().await; // waits for Ctrl+C
});
```
### Cancellation
```rust
let token = CancellationToken::new();
let child = token.child_token();
spawn_boxed(async move {
while !child.is_cancelled() {
do_work().await;
}
// cleanup
});
token.cancel(); // cancels all children
```
## JoinHandle
`spawn_boxed` and `spawn_slab` return `JoinHandle<T>`:
- **Await** — get the result: `let val = handle.await;`
- **Detach** — drop the handle, task continues, output dropped on completion
- **Abort** — `handle.abort()` consumes the handle, future dropped on next poll
- **Check** — `handle.is_finished()` for non-blocking status
`JoinHandle` is `!Send` and `!Sync` — stays on the executor thread.
## Performance
| Task dispatch (poll cycle) | 55-64 cycles |
| Local channel try_send+try_recv | 13 ns |
| MPSC channel try_send+try_recv | 22 ns |
| SPSC channel try_send+try_recv | 15 ns |
| Cross-thread channel (busy spin) | 15 ns |
| Cross-thread channel (park/epoll) | 1.7 us |
| Tokio-compat waker bridge | 76 ns |
## Features
| `tokio-compat` | No | Adapters for bridging tokio and nexus-async-rt in the same process |
## Dependencies
- **mio** — I/O event loop (epoll/kqueue)
- **nexus-rt** — World/WorldBuilder for typed resource storage
- **nexus-slab** — Optional pre-allocated task storage
- **nexus-timer** — Hierarchical timer wheel
- **nexus-queue** / **nexus-logbuf** — Lock-free internal queues
## Platform support
Unix only (`#![cfg(unix)]`). Linux is the primary target, macOS supported.