Spawn
The spawn keyword creates a concurrent task that runs in a separate OS thread. It can be used as a statement (fire-and-forget) or as an expression (returning a task handle).
Syntax
SpawnStmt = "spawn" Block
SpawnExpr = "spawn" Block
When spawn appears as a statement, the result is discarded. When it appears as an expression (e.g., assigned to a variable), it returns a TaskHandle.
Statement Form (Fire-and-Forget)
When spawn is used as a statement, the block is executed concurrently and its result is discarded:
spawn {
say "running in background"
}
say "continues immediately"
The parent does not wait for the spawned task to complete. The spawned block runs independently.
Expression Form (Task Handle)
When spawn is used as an expression, it returns a TaskHandle that can be awaited:
let handle = spawn {
return 42
}
let result = await handle // 42
The task handle is an opaque value that represents the running task. Its type name is "TaskHandle".
Execution Model
When spawn is executed:
- The block’s statements are cloned.
- A new
Interpreteris created and its environment is cloned from the parent. - A shared result slot is created:
Arc<(Mutex<Option<Value>>, Condvar)>. - A new OS thread is spawned via
std::thread::spawn. - The thread executes the block. When it completes:
Signal::Return(v)orSignal::ImplicitReturn(v)storesvin the result slot.Signal::Noneor other signals storenull.- Errors print to stderr and store
null.
- The
Condvaris notified, unblocking anyawaiton the handle. - The
TaskHandlevalue is returned to the parent.
Environment Cloning
The spawned task receives a clone of the parent’s environment at the point of the spawn call. This means:
- Variables defined before
spawnare accessible in the spawned block. - Modifications to the environment inside the spawned block do not affect the parent.
- Modifications in the parent after
spawndo not affect the spawned block.
let x = 10
spawn {
say x // 10 — sees parent's x
let x = 20 // shadows, does not affect parent
}
say x // 10 — parent's x unchanged
Return Values
The spawned block may use return to provide a result. This value is stored in the task handle’s result slot:
let h = spawn {
return "hello from spawn"
}
let msg = await h // "hello from spawn"
If no return is used, the result is the last expression value (implicit return) or null:
let h = spawn {
1 + 1
}
let result = await h // may be 2 or null depending on block signal
Error Isolation
Errors in spawned tasks are isolated from the parent. A runtime error in the spawned block:
- Prints the error message to stderr:
spawn error: <message>. - Stores
nullin the result slot. - Does not crash or affect the parent task.
spawn {
let x = 1 / 0 // error: division by zero
}
// Parent continues normally
say "still running"
When the handle is awaited, the result is null:
let h = spawn {
return 1 / 0
}
let result = await h // null (error was caught internally)
Multiple Spawns
Multiple tasks can be spawned and awaited:
let a = spawn { return 10 }
let b = spawn { return 20 }
let va = await a // 10
let vb = await b // 20
say va + vb // 30
Tasks run concurrently in separate threads. The order of completion is non-deterministic.
Spawn with Channels
Spawn and channels work together for structured concurrency:
let ch = channel()
spawn {
let result = expensive_computation()
send(ch, result)
}
// Do other work...
let result = receive(ch) // blocks until computation completes