KUJO THE COURSE

THE KUJO COURSE · VERIFIED 1.3.1

Match, Result, and Option

Give expected failure and absence their own states.

STAGE 2 / Language semantics · LESSON 10

Mental model

Expected failure is data the caller must handle. Absence is different again. Result models success or failure; Option models a present or absent value. Exceptions are useful for a different boundary, covered next.

Language contract

Use contextual constructors Ok, Err, Some, and None, and match their alternatives with case clauses. A binding inside a matched variant names its payload. Custom enums can use qualified variants such as State::Ready. Guards narrow a matching branch with an additional condition.

Do not assume Rust's exhaustiveness checking or ownership semantics. In a dynamic program, tests must show every important alternative is handled. Include a deliberate fallback for unexpected external states rather than silently treating them as success.

The example makes a positive quantity a successful result and an invalid quantity an expected error. The consumer converts the result into a plain output value. This is easier to test than scraping a printed sentence or catching every exception indiscriminately.

Choosing the right model

A missing optional label may be None. A supplied but invalid label may be Err with a reason. A storage failure can belong at an exception boundary. These distinctions preserve information for the caller, including whether it should ask for input, retry, or stop.

Professional pattern

Keep error payloads stable enough for programs to branch on. Prefer a code and bounded context over a single human sentence when crossing process boundaries. Render a friendly message separately. The machine-contract lesson develops this into versioned output.

Common mistakes

Returning Err is not the same as throwing. Calling a successful constructor does not validate its payload. A model can return a dictionary that claims success while containing invalid fields; your program must validate them before accepting the state.

Working example

func quantity(value) {
    if value > 0 { return Ok(value) }
    return Err("quantity must be positive")
}
match quantity(3) {
    case Ok(value): { print(value) }
    case Err(message): { throw(message) }
}
match Some("report") {
    case Some(label): { print(label) }
    case None: { print("unnamed") }
}


Run it

From the course repository root, use the pinned Kujo 1.3.1 runtime.

kujo check examples/10.kujo
kujo run --untrusted  examples/10.kujo

Captured output

3
report

Break it and diagnose it

An out-of-bounds lookup is a runtime error, not automatically None. Wrap an explicit bounds decision in an Option-returning function when absence is part of your API.

let value := [1]
print(value[2])

kujo run --untrusted  examples/10-break.kujo

Exit status: 4. Captured diagnostic:

[KUJOVM001] [vm] Runtime Error: Index out of bounds: 2
  --> 0:0


Exercise

Write a safe lookup returning Some or None. Write a separate quantity validator returning Ok or Err. Match every alternative and include a guarded success branch for a large quantity.

Checkpoint

  • I distinguish absence, expected rejection, and exception.
  • I can bind a variant payload.
  • I do not assume static exhaustiveness checking.

Contract versus release behavior

Built-in Result/Option matching passes here. A separate custom State::Ready("checked") probe reached the default branch on the release VM while the interpreter matched it. Qualified custom-enum patterns are therefore taught as a specified surface with a verified release discrepancy, not an interchangeable working substitute. See the evidence ledger for exact probes and runtime results.

Verified 2026-09-06 · Official Kujo 1.3.1 release · Source contract · Download example