Io 101
If you are going to use std.Io directly - this page for you.
(BTW - API Reference and Patterns pages assume your Io knowledge)
The four things std.Io gives you
Io access to the runtime — passed to anything that waits or spawns
Future(T) a result that isn't ready yet
Io.Select waits on several event sources at once
Io.Group runs several tasks, waits for all of them
-
Io— think of it as a capability token. Anything that needs threads, timers, or waiting takes one. -
Future(T)— you get the value by calling.await(). -
Io.Select— internally:queue: Queue(U)+group: Group.select.await()reads the next completed result from the queue. -
Io.Group— runs several tasks concurrently, waits for all of them, or cancels all of them.
Future — one asynchronous result
var fut = try io.concurrent(workerFn, .{&ctx});
// ... do other work ...
try fut.await(io); // blocks until worker exits; returns worker's return type
concurrent() ──► Future(T)
│
┌───────┼───────┐
V V
.await() .cancel(io)
│ │
V V
result error.Canceled
Rules:
argsis a tuple —.{arg1, arg2, ...}— passed verbatim to the function.-
io.concurrentcopiesargsbefore returning. Stack-allocated args are safe — no heap context needed. -
No
iois injected automatically. If the worker needs it, pass it explicitly:.{io, &ctx}. -
fut.await(io)returns the worker's return type directly. -
fut.cancel(io)injectserror.Canceledat the worker's next cancellation point, then awaits. -
Futureis a resource — callawaitorcancelexactly once.
Io.Group — several tasks, one wait
var group: std.Io.Group = .init;
defer group.cancel(io); // safe: no-op if already awaited
try group.concurrent(io, workerFn, .{&ctx1});
try group.concurrent(io, workerFn, .{&ctx2});
try group.concurrent(io, workerFn, .{&ctx3});
try group.await(io); // blocks until all workers exit
Rules:
-
Worker return type must be coercible to
Cancelable!void—void,!void, andCancelable!voidall work. -
error.Canceledreturned by a worker is swallowed at the Group boundary — it's a cancellation propagation stop, not an error you see. -
group.await(io)returnsCancelable!void. -
group.cancel(io)injectserror.Canceledinto every running worker, then waits. Safe to call again after anawait— a no-op. -
group.concurrentaftergroup.awaitstarts a new round in the same Group.
Io.Select — several event sources, one loop
var buf: [8]MasterEvent = undefined;
var sel: std.Io.Select(MasterEvent) = std.Io.Select(MasterEvent).init(io, &buf);
try sel.concurrent(.inbox, mailbox.receiveResult, .{ mbh, null });
try sel.concurrent(.pool_ev, pool.getWaitResult, .{ ph, TAG, null });
while (true) {
const event = try sel.await();
switch (event) {
.inbox => |r| { /* process, then re-register */ },
.pool_ev => |r| { /* process, then re-register */ },
}
}
The rhythm:
Internals (verified from std/Io.zig):
queue: Queue(U)— completed results land here.group: Group— owns every spawned concurrent task.select.await()isqueue.getOne(io)— blocks until the next result arrives.select.concurrent(field, fn, args)spawnsfnviagroup, wraps its return value as@unionInit(U, @tagName(field), result), and pushes it ontoqueue.
Direct push — no spawn, for a result that's already available or an external callback:
Event sources — the pattern behind Select
An event source is a blocking function passed to select.concurrent. When it returns,
the result lands in the queue.
blockingFn ────► select.concurrent(field, blockingFn, args)
│
V (fn runs, result → queue)
Io.Select.queue
│ │
V V
completed canceled
(result) (error.Canceled)
Two ways to produce an event:
select.concurrent(field, blockingFn, args)— spawn a blocking function.select.queue.putOneUncancelable(io, value)— push directly, no spawn.
Matryoshka's Mailbox and Pool plug into exactly this pattern — see
API Reference — Mailbox event source helpers and
API Reference — Pool event source helpers.
Cancellation
A function that waits — for data, for a timeout, for a condition — can be canceled by
the runtime.
-
If a function can be canceled, its return type includes
Cancelablein the error union. The signature is the only source of truth for this. -
Cancel is something you do to a Future or a Group — it never happens on its own.
concurrent() ──► Future(T)
│
┌───────┼───────┐
V V
.await() .cancel(io)
│ │
V V
result error.Canceled
Back to: Patterns — Futures, Select, Group, Cancellation for how
Matryoshka code actually uses these primitives.