# Chapter 9: Error Handling Flash treats errors as values rather than throwing exceptions, using Optionals for missing values, Error Unions for failures, and Defer blocks for cleanup. --- ## 1. Optionals (`?T`) An optional type represents a value that might either exist (`T`) or be empty (`null`). * **Spelling:** `?usize` or `?[*]u8` * **Unwrapping:** Use `orelse` to specify a fallback value: ```flash var name ?cstr = get_username() // Fallback to "guest" if name is null final_name := name orelse "guest" ``` --- ## 2. Error Unions (`!T`) An error union represents a value that is either a success value (`T`) or an error code. ```flash // A function that returns a u32 OR an error code fn parse(input []u8) !u32 { if input.len == 0 { return error.EmptyInput } // ... } ``` ### Named Error Sets (`error{…}` and `E!T`) The bare `!u32` above lets the compiler **infer** the error set. You can instead name a set explicitly with `error{…}` and join it to the success type with the infix `E!T` form: ```flash // A named set of the errors this routine can raise const ParseError = error{ EmptyInput, Overflow, } // The return type names the set explicitly: a ParseError, or a u32 fn parse(input []u8) ParseError!u32 { if input.len == 0 { return error.EmptyInput } // ... } ``` An individual error value is originated with `error.Name`. The optional (`?T`) and error-union (`!T`) markers compose, so a routine can return `?u32` (maybe a value), `!u32` (a value or an error), or `!?u32` (an error, or maybe a value). ### Try and Catch * **`try`**: Evaluates an expression. If it is an error, the function immediately returns that error. If successful, it evaluates to the unwrapped value. * **`catch`**: Handles an error inline and evaluates to a fallback value or code block. ```flash // Propagate the error up if read fails const bytes = try file.read() // Handle the error inline, falling back to 0 const size = file.read() catch 0 ``` ### Catch with Error Capture (`catch |err|`) You can capture the specific error code using the `catch |err|` syntax to inspect the error inside a fallback block: ```flash const size = file.read() catch |err| { if err == error.PermissionDenied { log("Access denied!") } return 0 } ``` The capture is optional. When the recovery does not need the specific error, `expr catch { … }` runs the block on any error without binding it: ```flash // Best-effort flush: ignore any error and carry on device.flush() catch {} ``` ### Error Capture on `if` and `while` (`else |err|`) When the condition of an `if` (or `while`) is an error union, the success payload binds in the usual `|value|` capture — and the `else` arm can bind the **error** with `else |err|`, completing the capture surface `catch |err|` opened: ```flash if file.read() |bytes| { process(bytes) } else |err| { log_error(err) } ``` A `for` loop's `else` arm takes no capture — there is no error to bind — and a stray one is rejected with a guiding diagnostic. --- ## 3. Resource Cleanup (`defer` / `errdefer`) To prevent memory leaks and ensure resources (like file handles) are closed, Flash inherits Zig's defer blocks. * **`defer`**: Schedules code to be executed when exiting the current block scope, regardless of how the block is exited. * **`errdefer`**: Schedules code to be executed *only* if the block exits with an error. ```flash use flibc fn copy_file(src_path cstr, dest_path cstr) !void { const src_fd = flibc.sys.open(src_path) if src_fd < 0 { return error.OpenFailed } // Ensure file is closed when function returns defer _ = flibc.sys.close(src_fd) const dest_fd = flibc.sys.open(dest_path) if dest_fd < 0 { return error.CreateFailed } defer _ = flibc.sys.close(dest_fd) // Copy logic... } ``` ### Block Form (`defer { … }`) Both keywords also accept a brace-delimited block, for deferring a sequence of statements. The block opens its own scope and runs whole on exit: ```flash defer { _ = flibc.sys.close(fd) log("closed") } ``` ### Error Capture on `errdefer` (`errdefer |err|`) An `errdefer` can see **which** error is unwinding, with the same capture-pipe shape as `catch`: ```flash errdefer |err| log_failure(err) errdefer |err| { log_failure(err) cleanup() } ``` The capture binds for the deferred code only and by value (`errdefer |*err|` is rejected, like every other error capture). A capture pipe on a plain `defer` gets its own targeted diagnostic — there is no error on a normal exit.