---
title: "Structured Concurrency"
description: "Understand Hew scope, fork, Task, await, deadlines, cooperative cancellation, select, and the boundary between tasks and actors."
source: "https://hew.sh/docs/concepts/structured-concurrency"
---

# Structured Concurrency

Concurrent work stays attached to a lifetime that settles it before returning.

Concurrency is easier to reason about when every child computation has an owner. In Hew, `fork` creates a child task, `await` takes that task's result, and the owning function or `scope` settles unfinished children before its lifetime ends.

## Run independent work together

Two child tasks, one scope

```hew
fn subtotal() -> i64 { 32 }
fn shipping() -> i64 { 10 }
 
fn main() {
    let total = scope {
        let items = fork subtotal();
        let delivery = fork shipping();
        (await items) + (await delivery)
    };
    println(total); // 42
}
```

Two tasks, one lifetime

`scope`owns both tasks

`fork subtotal()`↓**32**`await items`

`fork shipping()`↓**10**`await delivery`

`32 + 10 = 42`

↓Scope returns `42`

Both child tasks settle before the scope returns its value.

Both calls can make progress independently. Each `fork` returns a task immediately; each `await` consumes one task handle and yields its result. Before the scope returns `total`, it has no remaining child work.

## Understand the three parts

### `scope` owns a lifetime

A scope is an expression, so its last expression becomes its value. Normal exit waits for unfinished children. A fault or cancellation cancels affected children, waits for their cleanup, and only then leaves the scope or runs its failure handler.

Every function already provides an implicit task lifetime. An explicit `scope` creates a narrower boundary when a group of child operations should settle before the surrounding function continues.

### `fork` starts child work

A plain function or actor call still waits, even when written inside a scope. Only `fork` starts a child task. Arguments and captures are acquired before the child uses them: ordinary data becomes an independent value, while an affine owner transfers to the child.

### `await` takes one result

`await Task<T>` yields exactly `T`. If the child returns a `Result<T, E>`, awaiting it yields that same result for the application to handle; `await` does not add or flatten a `Result`. Awaiting a vector of tasks returns values in vector order.

## Handle deadlines and cancellation

Recover after a scope deadline

```hew
fn main() {
    let answer = scope within 20ms {
        sleep(1s);
        42
    } handle failure {
        0
    };
    println(answer);
}
```

The failure handler runs after the scope's children and cleanup have settled. It handles a structured deadline or fault, not an ordinary `Result.Err`. Parent cancellation keeps propagating outward; an inner handler cannot clear it.

Cancellation is cooperative. Suspending calls, task waits, loop headers, and compiler-inserted safepoints observe it. Long external calls and straight-line code without a safepoint can delay cancellation. The scope still waits for cancelled children to drain, including their resource cleanup.

### `select` waits for readiness

`select` chooses one ready task, actor call, stream receive, or timer arm. A losing task arm keeps running and remains owned by its scope; selection alone does not cancel it. Use a deadline around the work or `race` when the operation's contract should cancel and clean up losing work.

For all-of work, use batch `fork` or await a vector of tasks. See [Select and Join](/docs/compare/select-join) for side-by-side patterns.

## Keep task and actor lifetimes distinct

|  | Task | Actor |
| --- | --- | --- |
| Created by | `fork` | `spawn` |
| Lifetime | Bound to its owning function or scope | Independent of the caller's task scope |
| Wait for completion | `await task` | `stopped(actor)` for terminal cleanup |
| Ordinary call | Function call returns its value | Handler call returns a completion `Result` |
| Failure owner | Owning structured lifetime | Actor and its supervisor |

Forking an actor call makes the *request operation* a child task; it does not make the destination actor a child. Once the actor claims the request, abandoning the reply does not undo the handler's effects. Manage the actor itself with its lifecycle operations and supervisor.

**Advanced: values, faults, and cancellation are different outcomes**

An application error such as `Result.Err` is an ordinary task result. A task fault or scope cancellation follows the structured failure path. Keeping these separate lets a batch collect several application errors without turning the first one into automatic cancellation, while still guaranteeing cleanup for faults and deadlines.

A task handle is affine and cannot be sent as an actor message. Its result belongs to the structured lifetime that created it and can be joined once. If direct access to an unbound fork's result is discarded, the parent still drains the child; the work is not detached.

## Continue from here

-   Use the [actor guide](/docs/actors#tasks-and-structured-concurrency) for tasks started around actor calls.
-   Read [Actor Lifecycle and Supervision](/docs/concepts/actor-lifecycle) for long-lived components.
-   Check exact task, select, and cancellation syntax in the [language reference](/docs/language-spec#concurrency).
-   Look up released task and scope contracts in the [generated concurrency reference](https://docs.hew.sh/language.concurrency.html).
