Skip to content

Coroutines and Generators

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.

enespt-br