The map
Most names carry over; the import path changes. The last column is what to watch.
| What you use today | In Nuclei | Mind |
|---|---|---|
| An attribute on an async main or test | #[nuclei::main], #[nuclei::test] | threads = N and start_paused = true give a runtime of its own. |
| Run a future from synchronous code | nuclei::block_on | Allowed inside a task too: it nests and keeps the shard running. |
| Spawn and keep a join handle | nuclei::spawn gives a Task<T> | Awaiting gives T itself; join() gives Result<T, JoinError>. |
| Drop the handle to let the task run on | .detach() | Dropping a Task cancels it. |
| Abort a task, a join set | abort(), abort_handle(), nuclei::task::JoinSet | cancel().await also gives back an output that was ready. |
| Blocking work, tasks that are not Send | spawn_blocking, spawn_local | No local set to create: a local task runs on the calling thread's shard. |
| Spawn on a chosen worker, or on each | RuntimeHandle::spawn_on_worker, on_each_worker | A placed task stays on its worker for its whole life. See Runtime. |
| A value per worker thread | nuclei::shard_local! | It lives in the worker's shard and moves with it on a handoff; off the workers there is none. |
| Task-local values, yielding, the budget | nuclei::task_local!, task::yield_now, task::coop | A task-local value is set with scope around the task's future. |
| A runtime builder and handle | nuclei::Builder, RuntimeHandle | A handle does not keep the runtime running. See every setting. |
| TCP, UDP and Unix socket types | nuclei::net | std's own sockets inside; std's methods work through them. |
| Socket builders, stream halves | TcpSocket, UdpSocketBuilder, UnixSocket, split, into_split | A shared reference to a stream reads and writes too. |
| Shut one direction of a stream | shut(how), abort() | stream.shutdown(how) is std's call through the handle, which goes around the bytes written behind. See Networking. |
| Read and write extension traits, copies, buffers | nuclei::io | On the futures-io traits; closing a write side is shutdown. |
| Wait until readable, then try a read | readable(), try_read() | Rarely needed: recv().await completes the read for you. |
| A descriptor of your own, by readiness | nuclei::io::unix::AsyncFd | The descriptor must be in non-blocking mode. |
| Files and directories | nuclei::fs | read_dir gives entries with next_entry().await. |
| Reads and writes at an offset | read_at, write_at on &File | The handle's own calls are async; std's FileExt calls through it block the worker. See Files. |
| Sleep, timeout, interval, paused time | nuclei::time | A millisecond wheel; missed ticks burst by default. |
| Locks, semaphores, channels, a cancellation token | nuclei::sync | The established names and signatures; they work under any executor. |
| Channels and locks for tasks of one thread | nuclei::sync::local | Not Send, and cheaper for it: for spawn_local tasks of one shard. See Sync. |
| select!, join!, try_join! | nuclei::select!, join!, try_join! | select! starts at a random branch unless biased;. |
| Ctrl-C, Unix signals, child processes | nuclei::signal, nuclei::process | A signal is a stream: recv().await. |
Pitfalls
Dropping a task cancels it
The most common bug after a move. A task whose Task is dropped stops at its next .await, and never runs if it had not started. Every fire-and-forget spawn needs .detach(). See Tasks.
A write is taken, not delivered
A write returns once its bytes are copied into the stream's write-behind. Code that writes and then exits, or expects an error right away, should flush().await or shutdown().await. See How it works.
No work stealing
A task stays on its shard, and no idle core takes work from a busy one. A poll that runs past handoff_after (10 ms by default) has the shard's other tasks go on on another thread of the same core (Tasks), but the long poll still shares that core: give such work to spawn_blocking, or break it up.
Buffers are copied, unless you lend them
recv and read copy from the shard's buffer into your slice; writes copy into the write-behind. Where the copy matters, read_owned and write_owned lend the driver your own buffer, a vector or any IoBuf.
std's calls through the handle
A socket or file is std's own object in a Handle, so std's methods are reachable through it. The ones that read, write or shut a stream go around the bytes the handle read ahead and wrote behind, and the blocking ones block the worker. Use the handle's own calls; Networking lists what each of std's does.
Configure io_uring before any IO
Proactor::with_config runs once, before the first IO, spawn or block_on; it panics otherwise. Configure from a plain fn main, then enter the runtime.