Option & Result

Represent successful values, missing values, and recoverable errors without exceptions.

1

Core concepts

Option<T>, Some, None, Result<T, E>, Ok, Err, match

2

How the learner should approach this project

Do not paste the complete program immediately. Create the file, type the first step, run vpp check, and then run the program when a complete entry point exists. Read the explanation before looking at the snippet. After each step, predict what the new code should do. This turns the page into a lesson rather than a code dump.

3

Step 1: Return Result from division

Result is useful when a function can either produce a value or explain why it could not. safe_div returns Result<int, string>. A zero divisor becomes Err, while a valid division becomes Ok.

After typing the snippet, identify the new names introduced by the step. Ask what each name represents, what type the compiler should infer or check, and what value should exist after the code runs.

main.vpp
fn safe_div(a: int, b: int) -> Result<int, string> {
    if b == 0 {
        return Err("division by zero")
    }
    return Ok(a / b)
}
4

Step 2: Return Option from a search

Option represents presence or absence. find_first returns Some when it finds the target and None when the loop finishes without finding it.

The mutable index is necessary because the search advances one position at a time.

Before moving forward, explain how this step connects to the previous one. In particular, identify which values cross a function boundary, which values change, and which values are guaranteed by the type system.

main.vpp
fn find_first(nums: array[int], target: int) -> Option<int> {
    let mut i = 0
    while i < len(nums) {
        if nums[i] == target {
            return Some(nums[i])
        }
        i = i + 1
    }
    return None
}
5

Step 3: Match both safe values

The caller must handle the alternatives. Matching Result extracts the successful integer from Ok or the error string from Err. Matching Option extracts the value from Some or handles None.

Before moving forward, explain how this step connects to the previous one. In particular, identify which values cross a function boundary, which values change, and which values are guaranteed by the type system.

main.vpp
fn main() -> int {
    let r = safe_div(10, 2)
    match r {
        Ok(n) => {
            print(n)
        }
        Err(e) => {
            print(e)
        }
    }
    let nums = [1, 5, 9]
    let found = find_first(nums, 5)
    match found {
        Some(n) => {
            print(n)
        }
        None => {
            print("not found")
        }
    }
    return 0
}
6

Complete source

main.vpp
fn safe_div(a: int, b: int) -> Result<int, string> {
    if b == 0 {
        return Err("division by zero")
    }
    return Ok(a / b)
}
fn find_first(nums: array[int], target: int) -> Option<int> {
    let mut i = 0
    while i < len(nums) {
        if nums[i] == target {
            return Some(nums[i])
        }
        i = i + 1
    }
    return None
}
fn main() -> int {
    let r = safe_div(10, 2)
    match r {
        Ok(n) => {
            print(n)
        }
        Err(e) => {
            print(e)
        }
    }
    let nums = [1, 5, 9]
    let found = find_first(nums, 5)
    match found {
        Some(n) => {
            print(n)
        }
        None => {
            print("not found")
        }
    }
    return 0
}
7

Expected behavior

The complete program should produce the following output when run with the command shown below. Exact formatting should follow the current V++ runtime.

5
5
8

Run the project

terminal
vpp run projects/08-option-result/main.vpp
9

What the learner should understand after this project

Option models presence or absence. Result models success or failure. Matching makes the caller handle each alternative explicitly.

10

Common mistakes to teach

  1. Handling only Ok and forgetting Err
  2. Handling only Some and forgetting None
  3. Returning a plain value from a function that promises Option or Result
11

Practice extension

Call safe_div with zero and handle Err. Search for a value that is not present and handle None.

A strong learner should be able to explain the program without looking at the code, rebuild the core idea from memory, and make the practice change without copying a solution.

12

Full program

main.vpp
fn safe_div(a: int, b: int) -> Result<int, string> {
    if b == 0 {
        return Err("division by zero")
    }
    return Ok(a / b)
}
fn find_first(nums: array[int], target: int) -> Option<int> {
    let mut i = 0
    while i < len(nums) {
        if nums[i] == target {
            return Some(nums[i])
        }
        i = i + 1
    }
    return None
}
fn main() -> int {
    let r = safe_div(10, 2)
    match r {
        Ok(n) => {
            print(n)
        }
        Err(e) => {
            print(e)
        }
    }
    let nums = [1, 5, 9]
    let found = find_first(nums, 5)
    match found {
        Some(n) => {
            print(n)
        }
        None => {
            print("not found")
        }
    }
    return 0
}
vpp
$ ready. Click Test program.