Skip to content

Named Functions

A named function is declared with fn, takes typed parameters, and can declare the return type explicitly or let inference do the work. Functions can be declared in any order — the compiler makes two passes, so calling a function defined further below is valid:

Basic declaration, inferred return, and a function with no return (implicitly nil).

01-named.zolo
Playground
// Feature: Named functions
// Syntax: `fn name(p: T, ...) -> R { ... }`
// When to use: bread-and-butter — a reusable block, top-level
// or nested, with typed parameters and an optional return.

// Function with explicit types on parameters and return.
fn add(a: int, b: int) -> int {
  return a + b
}

print(add(2, 3))  // 5

// Return type can be omitted — inference handles it.
fn double(x: int) {
  return x * 2
}

print(double(7))  // 14

// No return: "void" function (returns implicit nil).
fn shout(msg: str) {
  print("!! {msg} !!")
}

shout("zolo")  // !! zolo !!

// Functions can be declared in any order — the
// compiler does two passes. `top` calls `helper`
// declared after it just fine.
fn top() -> int {
  return helper(10)
}

fn helper(n: int) -> int {
  return n + 1
}

print(top())  // 11

Sometimes auxiliary logic only makes sense inside another function. Using a local lambda keeps the scope clean and makes it clear that the helper is an implementation detail:

Local helpers via lambda — access to the outer scope without polluting the global namespace.

12-nested-functions.zolo
Playground
// Feature: Nested functions (local helpers)

// Syntax: `let helper = |...| { ... }` inside another fn,

// or simply declaring and using it immediately.

// When to use: a private helper that only makes sense inside

// the scope of another function — without polluting the global

// namespace.


// Local helper via lambda — scope restricted to `outer`.

fn outer(x: int) -> int {
  let inner = |y: int| y * 2
  return inner(x) + 1
}

print(outer(5))  // 11


// Helper that uses variables from the outer scope.

fn process(items: [int]) -> int {
  var total = 0

  let add = |n| {
    total = total + n
  }

  for it in items {
    add(it * 2)
  }
  return total
}

print(process([1, 2, 3, 4]))  // 20


// Decompose steps with local helpers (readability).

fn render_user(name: str, age: int) -> str {
  let header = |n| "[USER: {n}]"
  let body = |a| "  age = {a}"

  let h = header(name)
  let b = body(age)
  return "{h}\n{b}"
}

print(render_user("Alice", 30))

// expected:

// [USER: Alice]

//   age = 30


// Recursive helper via closure.

fn count_down(from: int) {
  let step = |n: int| {
    if n < 0 { return }
    print(n)
  }
  var i = from
  while i >= 0 {
    step(i)
    i -= 1
  }
}

count_down(3)
// expected: 3 2 1 0

Challenge

Create a function clamp(x: int, lo: int, hi: int) -> int that ensures lo <= result <= hi. Test it with values below, within, and above the range.

enespt-br