Option & Result
Represent successful values, missing values, and recoverable errors without exceptions.
Core concepts
Option<T>, Some, None, Result<T, E>, Ok, Err, match
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.
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.
fn safe_div(a: int, b: int) -> Result<int, string> {
if b == 0 {
return Err("division by zero")
}
return Ok(a / b)
}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.
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
}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.
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
}Complete source
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
}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
Run the project
vpp run projects/08-option-result/main.vppWhat 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.
Common mistakes to teach
- Handling only Ok and forgetting Err
- Handling only Some and forgetting None
- Returning a plain value from a function that promises Option or Result
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.
Full program
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
}