Skip to content

Multiple Returns and Recursion

In Zolo, multiple return is modeled as a homogeneous array: the return type is [T] and the caller destructures with let (a, b) = f(). This avoids creating a struct just to group two or three related values:

divmod, min_max and version — positional destructuring.

05-multi-return.zolo
Playground
// Feature: Multiple return via array

// Syntax: `fn f() -> [T] { return [a, b, c] }` + `let (x, y, z) = f()`

// When to use: return two or three related values without

// creating a struct — the classic div/mod, min/max, ok+value.

//

// In Zolo, "tuples" at the value site are arrays: the return type

// is `[T]` (homogeneous array) and destructuring extracts by position.


fn divmod(a: int, b: int) -> [int] {
  return [a / b, a % b]
}

let (q, r) = divmod(17, 5)
print("{q} remainder {r}")  // 3 remainder 2


// Min/max in a single pass.

fn min_max(arr: [int]) -> [int] {
  var lo = arr[0]
  var hi = arr[0]
  for x in arr {
    if x < lo { lo = x }
    if x > hi { hi = x }
  }
  return [lo, hi]
}

let (lo, hi) = min_max([3, 1, 4, 1, 5, 9, 2, 6])
print("lo={lo} hi={hi}")  // lo=1 hi=9


// Triples work the same — destructure by position.

fn version() -> [int] {
  return [1, 2, 3]
}

let (maj, min, patch) = version()
print("v{maj}.{min}.{patch}")  // v1.2.3


// For heterogeneous types, use a typed array as `[any]`-like

// (without annotation the inferrer accepts mixed literals).

fn lookup(ok: bool) -> [str] {
  if ok {
    return ["ok", "all good"]
  }
  return ["err", "went wrong"]
}

let (status, msg) = lookup(true)
print("{status}: {msg}")  // ok: all good

Recursion works naturally: a function can call itself, and the compiler resolves references in any order. For cases where the call depth would be high, the accumulator form (tail-style) is preferred. Mutual recursion — two functions that call each other — is also supported:

Factorial, naive Fibonacci and with accumulator, mutually recursive is_even/is_odd.

09-recursion.zolo
Playground
// Feature: Recursion (direct and mutual)
// Syntax: `fn f() { ...f()... }` — fn can call itself.
// When to use: naturally recursive structures (trees,
// lists), divide-and-conquer, mathematical definitions.

// Classic factorial.
fn factorial(n: int) -> int {
  if n <= 1 { return 1 }
  return n * factorial(n - 1)
}

print(factorial(5))  // 120
print(factorial(10))  // 3628800

// Naive Fibonacci (just for show, not efficient).
fn fib(n: int) -> int {
  if n < 2 { return n }
  return fib(n - 1) + fib(n - 2)
}

for i in 0..10 {
  print("fib({i}) = {fib(i)}")
}

// Fibonacci with accumulator ("tail-style" recursion).
fn fib_iter(n: int, a: int, b: int) -> int {
  if n == 0 { return a }
  return fib_iter(n - 1, b, a + b)
}

fn fib_fast(n: int) -> int {
  return fib_iter(n, 0, 1)
}

print(fib_fast(20))  // 6765

// MUTUAL recursion — two functions calling each other.
fn is_even(n: int) -> bool {
  if n == 0 { return true }
  return is_odd(n - 1)
}

fn is_odd(n: int) -> bool {
  if n == 0 { return false }
  return is_even(n - 1)
}

print(is_even(10))  // true
print(is_odd(7))  // true

// Sum of array via recursion.
fn sum_arr(arr: [int], i: int) -> int {
  if i >= arr.len() { return 0 }
  return arr[i] + sum_arr(arr, i + 1)
}

print(sum_arr([1, 2, 3, 4, 5], 0))  // 15

Challenge

Write powers(base: int, max: int) -> [int] that returns [base^0, base^1, ..., base^max] using recursion, and destructure the first three results when calling it.

enespt-br