# Chapter 8: Error Handling Errors happen. Lux provides multiple ways to handle them, all explicit in the type system. ## The Fail Effect The simplest error handling uses the built-in `Fail` effect: ```lux fn divide(a: Int, b: Int): Int with {Fail} = if b == 0 then Fail.fail("Division by zero") else a / b fn main(): Unit with {Console} = { let result = run divide(10, 2) with {} Console.print("Result: " + toString(result)) } ``` When `b == 0`, `Fail.fail` stops execution. The program terminates with an error. ### Fail Propagates ```lux fn helper(): Int with {Fail} = Fail.fail("Oops") fn caller(): Int with {Fail} = { let x = helper() // Fails here x * 2 // Never reached } ``` If you call a function with `Fail`, you must declare it: ```lux // Error: caller uses Fail but doesn't declare it fn broken(): Int = { divide(10, 0) } ``` ## Option - Maybe There's a Value For operations that might not have a result: ```lux fn safeDivide(a: Int, b: Int): Option = if b == 0 then None else Some(a / b) fn findUser(id: Int): Option = // Returns None if user doesn't exist Database.query("SELECT * FROM users WHERE id = " + toString(id)) ``` ### Working with Option **Pattern matching:** ```lux fn showResult(opt: Option): String = match opt { Some(n) => "Got: " + toString(n), None => "No value" } ``` **Option methods:** ```lux let x = Some(5) let y: Option = None Option.map(x, fn(n: Int): Int => n * 2) // Some(10) Option.map(y, fn(n: Int): Int => n * 2) // None Option.getOrElse(x, 0) // 5 Option.getOrElse(y, 0) // 0 Option.isSome(x) // true Option.isNone(y) // true ``` **Chaining with flatMap:** ```lux fn getUserName(id: Int): Option = Option.flatMap(findUser(id), fn(user: User): Option => Some(user.name) ) ``` ## Result - Success or Failure For operations that can fail with an error value: ```lux fn parseNumber(s: String): Result = if isNumeric(s) then Ok(parseInt(s)) else Err("Not a number: " + s) fn readConfig(path: String): Result with {File} = if File.exists(path) then match parseConfig(File.read(path)) { Some(c) => Ok(c), None => Err("Invalid config format") } else Err("Config file not found: " + path) ``` ### Working with Result **Pattern matching:** ```lux fn handleResult(r: Result): String = match r { Ok(n) => "Success: " + toString(n), Err(e) => "Error: " + e } ``` **Result methods:** ```lux let success = Ok(42) let failure: Result = Err("oops") Result.map(success, fn(n: Int): Int => n * 2) // Ok(84) Result.map(failure, fn(n: Int): Int => n * 2) // Err("oops") Result.getOrElse(success, 0) // 42 Result.getOrElse(failure, 0) // 0 Result.isOk(success) // true Result.isErr(failure) // true ``` **Chaining operations:** ```lux fn processData(input: String): Result = { let parsed = parseInput(input) // Result let validated = Result.flatMap(parsed, validate) // Result Result.map(validated, transform) // Result } ``` ## Combining Approaches ### Option to Result ```lux fn optionToResult(opt: Option, error: String): Result = match opt { Some(v) => Ok(v), None => Err(error) } fn findUserOrError(id: Int): Result = optionToResult(findUser(id), "User not found: " + toString(id)) ``` ### Result to Option ```lux fn resultToOption(r: Result): Option = match r { Ok(v) => Some(v), Err(_) => None } ``` ### Fail with Result ```lux fn processOrFail(input: String): Output with {Fail} = match process(input) { Ok(output) => output, Err(e) => Fail.fail(e) } ``` ## Error Handling Patterns ### Early Return with Fail ```lux fn validateUser(user: User): User with {Fail} = { if user.name == "" then Fail.fail("Name required") else if user.age < 0 then Fail.fail("Invalid age") else if user.email == "" then Fail.fail("Email required") else user } fn registerUser(user: User): UserId with {Fail, Database} = { let validated = validateUser(user) Database.insert(validated) } ``` ### Collecting Errors ```lux fn validateAll(user: User): Result> = { let errors: List = [] let errors = if user.name == "" then List.concat(errors, ["Name required"]) else errors let errors = if user.age < 0 then List.concat(errors, ["Invalid age"]) else errors let errors = if user.email == "" then List.concat(errors, ["Email required"]) else errors if List.isEmpty(errors) then Ok(user) else Err(errors) } ``` ### Default Values ```lux fn getConfig(key: String): String = Option.getOrElse(Config.get(key), "default") fn getPort(): Int = Result.getOrElse(parseNumber(Process.env("PORT")), 8080) ``` ### Logging Errors ```lux fn processWithLogging(input: String): Result with {Console} = { let result = process(input) match result { Ok(_) => result, Err(e) => { Console.print("Error processing input: " + e) result } } } ``` ## Custom Error Types Define specific error types: ```lux type ValidationError = | MissingField(String) | InvalidFormat(String, String) // field, expected format | OutOfRange(String, Int, Int) // field, min, max fn validate(user: User): Result = { if user.name == "" then Err(MissingField("name")) else if user.age < 0 then Err(OutOfRange("age", 0, 150)) else if !isValidEmail(user.email) then Err(InvalidFormat("email", "user@domain.com")) else Ok(user) } fn showError(e: ValidationError): String = match e { MissingField(f) => "Missing required field: " + f, InvalidFormat(f, fmt) => f + " must be in format: " + fmt, OutOfRange(f, min, max) => f + " must be between " + toString(min) + " and " + toString(max) } ``` ## When to Use What | Scenario | Use | |----------|-----| | Might not exist | `Option` | | Can fail with reason | `Result` | | Fatal error, stop execution | `Fail` effect | | Multiple error types | Custom error ADT | ## Summary ```lux // Option - maybe a value let opt: Option = Some(42) let none: Option = None // Result - success or error let ok: Result = Ok(42) let err: Result = Err("failed") // Fail effect - abort execution fn risky(): Int with {Fail} = Fail.fail("boom") // Pattern match to handle match result { Ok(v) => handleSuccess(v), Err(e) => handleError(e) } ``` ## Next [Chapter 9: Standard Library](09-stdlib.md) - Built-in functions and modules.