Skip to content

Result: success or error as a value

Result<T, E> is an enum with two variants: Result.Ok(value) for success and Result.Err(error) for failure. Using Result as a return type lets the compiler require callers to handle both cases — nothing is silenced.

Mind the punctuation: use dot (.) to constructResult.Ok(v) — and double colon (::) to matchResult::Ok(v).

The example below shows how to construct variants, check the state, and safely extract the value:

Constructors, is_ok / is_err, unwrap, unwrap_or and a function that returns Result.

01-result-basics.zolo
Playground
// Feature: Result — success or error as a value
// Syntax: `Result.Ok(v)` / `Result.Err(e)`
// When to use: functions that may fail in an expected way.

use std::Result

// `Result` is an enum with two variants:
//   - `Result.Ok(value)`   success
//   - `Result.Err(error)`  failure

let ok = Result.Ok(42)
let err = Result.Err("something went wrong")

// -- Check the variant -----------------------------------------
print(ok.is_ok())  // true
print(ok.is_err())  // false
print(err.is_ok())  // false
print(err.is_err())  // true

// -- Extract the value ----------------------------------------
print(ok.unwrap())  // 42 — panic if Err
print(err.unwrap_or(0))  // 0  — default if Err
print(err.unwrap_err())  // "something went wrong"

// -- Function that returns Result -----------------------------
fn divide(a: int, b: int) -> Result<int, str> {
  if b == 0 {
    return Result.Err("division by zero")
  }
  return Result.Ok(a / b)
}

let r1 = divide(10, 2)
let r2 = divide(10, 0)
print(r1.is_ok())  // true
print(r1.unwrap())  // 5
print(r2.is_err())  // true
print(r2.unwrap_err())  // division by zero

// -- Read with unwrap_or — provides a safe default ------------
print(divide(100, 4).unwrap_or(-1))  // 25
print(divide(100, 0).unwrap_or(-1))  // -1

With match you handle both cases exhaustively — the compiler rejects code that omits one of the arms:

match as a statement and as an expression; bindings with descriptive names.

02-result-match.zolo
Playground
// Feature: Match on Result
// Syntax: `match r { Result::Ok(v) => ..., Result::Err(e) => ... }`
// When to use: handle success and failure exhaustively.

use std::Result

fn divide(a: int, b: int) -> Result<int, str> {
  if b == 0 {
    return Result.Err("division by zero")
  }
  return Result.Ok(a / b)
}

// -- Exhaustive match ------------------------------------------
fn describe(r: Result<int, str>) {
  match r {
    Result::Ok(v) => print("ok: {v}"),
    Result::Err(e) => print("error: {e}"),
  }
}

describe(divide(10, 2))
// expected: ok: 5
describe(divide(10, 0))

// expected: error: division by zero

// Note the match syntax: `Result::Ok` (with `::`), not `.`.
// To CONSTRUCT, use `Result.Ok(v)` (with `.`).

// -- Match as expression ---------------------------------------
fn format(r: Result<int, str>) -> str {
  return match r {
    Result::Ok(v) => "value = {v}",
    Result::Err(_) => "(failed)",
  }
}

print(format(divide(10, 2)))  // value = 5
print(format(divide(10, 0)))  // (failed)

// -- Bindings with different names -----------------------------
let r = divide(20, 4)
match r {
  Result::Ok(number) => print("computed {number}"),
  Result::Err(reason) => print("failed: {reason}"),
}
// expected: computed 5

Challenge

Add a third call describe(divide(10, 10)) and verify that the Ok arm prints ok: 1. Then replace unwrap() with unwrap_or(99) on r2 — what is the output?

enespt-br