Types
A scope that owns detached work (std.task.Group).
spawn hands a future to the runtime, which releases its captures when the work completes. A group is the opt-in for making this scope the owner instead, so that leaving the scope waits for the work it started. Groups are never required: a detached spawn stays legal and needs no group at all.
The field is a Future rather than an integer handle so the collector can reach the group's work without learning anything new: the keeper is traced as an ordinary struct field, its task_roots hold the children, and cancelFutureTree already walks those. An integer would form no edge at all, and a synthetic builtin declaration would join the Channel/Isolate exemption that plan 205 cut every other std type away from.
T is the type its children return. It is a phantom parameter — the one field is the keeper, which is Future<void> whatever the children produce — and exists to relate spawn's argument to what next and for hand back. A group starts with T open and the first spawn fixes it, exactly as a channel's first send fixes its message type.
_keeper: Future<void> |
|---|
Functions
Creates a new group that owns the work spawned into it.
use std.http_server
use std.task
async fn serve_one(request: http_server.Request) -> int {
http_server.respond(request, http_server.text(200, "ok"))
1
}
async fn serve(listener: int) -> int {
let jobs = task.new()
defer task.cancel(jobs)
while true {
let request = http_server.accept_stream(listener)
task.spawn(jobs, serve_one(request))
}
0
}Takes the next child to settle, in completion order.
group: Group to pull one result from.
some(result) is the next child to finish, whichever one that was; none means the group has no undelivered child left. A child that failed arrives as err, not as a propagated failure — a stream keeps going after one child fails, which is the whole difference between iterating a group and awaiting a barrier.
for result in group is this function in a loop, and is the spelling to reach for. Pull directly only when the loop shape is in the way.
use std.http
use std.task
async fn fetch(url: string) -> string {
http.get(url).body
}
fn save(body: string) -> int {
body.len()
}
async fn fetch_all(urls: [string]) -> int {
let jobs = task.new()
defer task.cancel(jobs)
for url in urls { task.spawn(jobs, fetch(url)) }
let mut saved = 0
for result in jobs {
match result {
ok(body) => { saved += save(body) },
err(e) => println("failed: " + e.message),
}
}
saved
}Results arrive as they finish, so a slow child never holds up one that is already done. Spawning into a group while iterating it extends the same iteration rather than racing it, which is what makes a self-feeding worker pool expressible.
Returns the number of children that have not settled.
group: Group whose outstanding count is returned.
Waits for every child to settle.
group: Group to drain.
Use this when the work should finish. A child that never returns — an accept loop, say — will not finish on its own, so cancel is the exit for those.
Cancels every child, then waits for them to notice.
group: Group to cancel.
Cancellation is a request. A child parked on an I/O wait stops at once; one that is running observes it at its next is_cancelled(), so this still waits rather than returning immediately.
The two halves are separable, and usefully so: calling this requests the cancellation, and awaiting the result is what waits. So defer task.cancel(group) tears a scope's work down on every exit path without waiting for it, and defer await task.cancel(group) waits — a deferred statement may suspend since 1.20.0.