Usage / Tutorial 5: concurrency basics (async, task, yield, await)

Tutorial 5: concurrency basics (async, task, yield, await)

Silk’s concurrency model is built from two orthogonal modifiers:

  • async — “this function can be awaited” (concurrency / pausable execution)
  • task — “this function can run as a task” (parallel work / worker execution)

Calling a concurrency-marked function produces a handle:

  • calling a task fn ... -> T produces a Task(T)
  • calling an async fn ... -> T produces a Promise(T)

You then use:

  • yield to receive values from tasks (and to send values from inside a task)
  • await to unwrap promises

Reference: Concurrency.

1) A minimal task + receiver#

Create concurrency_basic.slk:

task fn add (a: int, b: int) -> int {
  return a + b;
}

async fn main () -> int {
  let h = add(1, 2); // h: Task(int)

  // `task { ... }` establishes a task context so `yield` is available.
  task {
    let v: int = yield h;
    if v != 3 { return 1; }
    return 0;
  }
}

Build and run:

silk check concurrency_basic.slk
silk build concurrency_basic.slk -o build/concurrency_basic
./build/concurrency_basic

What to notice:

  • Calling add(1, 2) does not immediately give you an int; it gives you a handle.
  • yield h receives the task’s produced value.
  • The task/async split keeps “what can block” explicit in the code.

2) A minimal promise + await#

Promises are the async counterpart:

async fn answer () -> int { return 42; }

async fn main () -> int {
  let p = answer();     // p: Promise(int)
  let v: int = await p; // unwrap the promise
  if v != 42 { return 1; }
  return 0;
}

Why this model is valuable#

Silk’s concurrency keywords are intentionally explicit because they communicate “shape”:

  • If a function is async, callers know they’re getting a promise-like handle.
  • If a function is task, callers know they’re spawning work that must be joined/drained.
  • yield/await make synchronization points visible in code review (no hidden “maybe blocks here”).

Current boundaries#

  • await and structured async { ... } / task { ... } blocks are allowed only inside async functions.
  • Promise(T) handles are consumed by await; Task(T) handles are drained/joined by yield *. Handles are single-use and cannot be copied.
  • On hosted Linux x86_64, await uses the single-threaded fiber executor and can suspend without blocking its OS thread. yield and yield * are task-context-only operations and remain blocking OS-thread waits.

The full ownership, suspension-safety, and structured-cleanup rules are in Concurrency.

Next#

Source repository · Edit this page · View Markdown