12: Error Handling with Results

Audience: All Time: 120 minutes Prerequisites: 02-Values, 11-Nil-Tracking You'll learn: Result type, Ok/Err pattern, error propagation, error chains


The Big Picture

Instead of exceptions that crash, Zebra uses Result types that make errors explicit:

Result(T, E) = Ok(T) | Err(E)

Either you have a value of type T, or an error of type E.


Basic Results

// file: 12_result_basic.zbr

// teaches: result type // chapter: 12-Error-Handling-with-Results

class Validator shared def parse_int(text as str) as Result(int, str) if text.len == 0 return Result.err("Empty string") # Simplified: real parsing more complex var value as int = 42 return Result.ok(value)

class Main shared def main var result = Validator.parse_int("123")

if result.isOk() var value = result.okValue() print "Parsed: ${value}" elif result.isErr() var error = result.errValue() print "Error: ${error}"


Unwrap Safely

// file: 12_result_unwrap.zbr

// teaches: result unwrapping // chapter: 12-Error-Handling-with-Results

class Main shared def main var result = Validator.parse_int("42")

# Option 1: Check and use if result.isOk() var val = result.okValue() print val

# Option 2: Get with default var val = result.unwrapOr(0) print val

# Option 3: Unwrap (crashes if error) # var val = result.unwrap()


Error Propagation

!Error Propagation Flow

// file: 12_error_propagation.zbr

// teaches: propagating errors // chapter: 12-Error-Handling-with-Results

class Parser shared def parse_config(text as str) as Result(str, str) # Parse JSON/config if text.len == 0 return Result.err("Empty config") return Result.ok("parsed")

class System shared def load_system(config_text as str) as Result(str, str) var parsed = Parser.parse_config(config_text) if parsed.isErr() return Result.err(parsed.errValue())

var data = parsed.okValue() # Continue processing return Result.ok("System loaded")

class Main shared def main var system = System.load_system("") if system.isErr() print "Failed: ${system.errValue()}" else print system.okValue()


Real World: API Client

// file: 12_api_client.zbr

// teaches: results in realistic code // chapter: 12-Error-Handling-with-Results

class APIClient shared def fetch_user(user_id as int) as Result(str, str) if user_id <= 0 return Result.err("Invalid user ID") # Simulate API call if user_id == 1 return Result.ok("Alice") return Result.err("User not found")

def fetch_and_greet(user_id as int) as Result(str, str) var user_result = fetch_user(user_id) if user_result.isErr() return Result.err(user_result.errValue())

var user = user_result.okValue() var greeting = "Hello, ${user}!" return Result.ok(greeting)

class Main shared def main var result = APIClient.fetch_and_greet(1) print result.unwrapOr("Error: Could not greet")


Exercises

Exercise 1: Safe String to Int

Solution

class Parser

shared def string_to_int(text as str) as Result(int, str) if text.len == 0 return Result.err("Empty string") # Real impl would parse digits if text == "abc" return Result.err("Not a number") return Result.ok(42)

class Main shared def main var r1 = Parser.string_to_int("42") var r2 = Parser.string_to_int("abc") print r1.unwrapOr(0) print r2.unwrapOr(0)


Next Steps

- → 13-Generics — Type-safe containers - → 15-Pipelines — Chain operations gracefully


Key Takeaways

- Result(T, E) — Either value or error - Check before unwrapping — Use isOk(), isErr() - Propagate errors — Pass them up the call stack - unwrapOr() — Safe default fallback - Errors are values — Not exceptions


Next: Head to 13-Generics for type-safe abstractions.