Markdown 147 lines
# 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.