Skip to content

Coroutines and Generators

On this page

The coroutine is the building block of concurrency in Zolo. With coroutine.create, coroutine.resume, and coroutine.yield you can suspend and resume functions arbitrarily — no OS threads, no data locks.

The lifecycle is simple: suspendedrunningsuspended (at each yield) → dead (when the body returns).

Creation, resume with and without arguments, and status inspection with coroutine.status.

01-coroutines-basic.zolo
Playground
// Feature: Coroutines (create / resume / yield)

// Syntax: `coroutine.create(fn)`, `coroutine.resume(co)`, `coroutine.yield(v)`

// When to use: lightweight state machines, pause and resume work,

// pull-based pipelines, simulating threads without OS threads.

//

// A coroutine is a suspended function. Calling `resume` runs it

// until the next `yield` (or return). Each `yield` "pushes"

// a value back to whoever called `resume`.


use std::coroutine

// Simple coroutine — produces three values and ends.

let co = coroutine.create(|| {
  coroutine.yield("first")
  coroutine.yield("second")
  coroutine.yield("third")
})

print(coroutine.resume(co))  // first

print(coroutine.resume(co))  // second

print(coroutine.resume(co))  // third

print(coroutine.status(co))  // dead


// Coroutine with arguments — `resume` passes, `yield` returns.

let echo = coroutine.create(|x: int| {
  var n = x
  while n > 0 {
    coroutine.yield(n)
    n = n - 1
  }
})

print(coroutine.resume(echo, 3))  // 3

print(coroutine.resume(echo))  // 2

print(coroutine.resume(echo))  // 1


// Status: "suspended" before the first resume, "dead" after it returns.

let lazy = coroutine.create(|| {
  coroutine.yield(42)
})
print(coroutine.status(lazy))  // suspended

coroutine.resume(lazy)
print(coroutine.status(lazy))  // suspended (still has a pending yield)

coroutine.resume(lazy)  // finishes the body

print(coroutine.status(lazy))  // dead

Generators (fn*) are a syntactic sugar layer on top of coroutines aimed at lazy sequences. The function returns a handle that, when called, advances to the next yield. When the body finishes, it returns nil.

This allows infinite sequences: work only happens when the caller asks for the next value.

Finite generator, infinite Fibonacci, string generator, and a generator+filter pipeline.

02-generators.zolo
Playground
// Feature: Generators (`fn*`) with yield

// Syntax: `fn* name(params) { yield expr }`

// When to use: lazy/infinite sequences, streams, pipelines where

// we do not want to materialize everything in memory.

//

// A generator is a function that produces values on demand. Each

// call to the returned handle advances to the next `yield`. When

// the function ends, it returns `nil`.


// Finite generator.

fn* counter(limit: int) {
  var i = 0
  while i < limit {
    yield i
    i += 1
  }
}

let gen = counter(4)
print(gen())  // 0

print(gen())  // 1

print(gen())  // 2

print(gen())  // 3

print(gen())  // nil (exhausted)


// INFINITE generator — possible because it is lazy.

fn* fibonacci() {
  var a = 0
  var b = 1
  loop {
    yield a
    let temp = a + b
    a = b
    b = temp
  }
}

let fib = fibonacci()
for i in 0..8 {
  print("fib({i}) = {fib()}")
}

// expected: fib(0) = 0 ... fib(7) = 13


// String generator — any type works.

fn* statuses() {
  yield "loading"
  yield "ready"
  yield "done"
}

let st = statuses()
var s = st()
while s != nil {
  print("status: {s}")
  s = st()
}

// Pipeline: generator + filter.

fn* range_step(start: int, stop: int, step: int) {
  var i = start
  while i < stop {
    yield i
    i += step
  }
}

let evens = range_step(0, 10, 2)
var v = evens()
while v != nil {
  print("even: {v}")
  v = evens()
}

Challenge

Write a generator fn* primes() that yields prime numbers indefinitely. Use a manual take to print the first 10.

Search Zolo

9 results

enespt-br