
Functions signal problems by raising errors, and callers catch them with `try`/`except` blocks. When you want errors as values instead of exceptions, there is also `Result[T, E]` with `Ok` and `Err` variants.

## Try / except / finally

```vary
def divide(a: Int, b: Int) -> Int {
    if b == 0 {
        raise ValueError("division by zero")
    }
    return a // b
}

try {
    let result = divide(10, 0)
} except ValueError as e {
    print("Bad value")
} except Error as e {
    print("Some other error")
} finally {
    print("Always runs")
}
```

`except Error:` is the broadest catch. Bare `except:` is not supported. You can raise a string directly: `raise "message"` wraps it in `RuntimeError`.

## Error types

| **Type** | **Use case** |
|------|----------|
| `Error` | Base of all Vary errors |
| `RuntimeError` | General runtime errors |
| `TypeError` | Type mismatches |
| `ValueError` | Invalid values |
| `IOError` | I/O failures |
| `KeyError` | Missing dictionary keys |
| `IndexError` | Out-of-bounds access |
| `ContractViolation` | Failed [precondition or postcondition](/docs/contracts/) |
| `ProcessError` | Subprocess execution failure (not found, permission denied, timeout) |
| `TimeoutError` | Task group exceeded its timeout |

## Result type

`Result[T, E]` represents success or failure without exceptions:

```vary
def divide(a: Int, b: Int) -> Result[Int, Str] {
    if b == 0 {
        return Err("division by zero")
    }
    return Ok(a // b)
}

let r = divide(10, 3)
assert r.is_ok()
assert r.unwrap() == 3
```

| **Method** | **Returns** | **Description** |
|--------|---------|-------------|
| `.is_ok()` | `Bool` | True if Ok |
| `.is_err()` | `Bool` | True if Err |
| `.unwrap()` | `T` | Unwrap Ok value (panics on Err) |
| `.unwrap_err()` | `E` | Unwrap Err value (panics on Ok) |
| `.unwrap_or(default)` | `T` | Unwrap Ok, or return default |

Match on Result with `case Ok(value)` and `case Err(error)`.

## The `?` operator

The `?` postfix operator unwraps `Ok` or returns `Err` early:

```vary-snippet
def process() -> Result[None, FsError] {
    let wp = WritePath.of("/tmp/out.txt")?
    let _w = fs.write_text(wp, "hello")?
    return Ok(None)
}
```

The enclosing function must return a `Result` with the same error type.

## The `?else return` operator

When unwrapping a `Result` or optional, `?else return` provides a default value and exits the function early if the unwrap fails:

```vary-snippet
def get_name(id: Int) -> Str {
    let user = find_user(id) ?else return "unknown"
    return user.name
}
```

This is useful when the enclosing function does not return a `Result` and you want a fallback instead of propagating the error.
