---
name: Error handling
description: Describes Agency's exception-free approach to errors using the `Result` type, with `success`/`failure` constructors and patterns for unwrapping results.
---

# Error handling
Agency does not have exceptions:

- Exceptions can crash your program
- Exceptions can't be represented in the type system

Instead Agency has the `Result` type.

## The `Result` type

When you write a function you can either return a plain value:

```ts
def divide(a: number, b: number): number {
  return a / b;
}
```

Now if users divide by zero, you're out of luck. Instead, you can return a `Result`, which can be a `success` or a `failure`:

```ts
def divide(a: number, b: number): Result {
  if (b == 0) {
    return failure("Can't divide by zero!")
  }
  return success(a / b)
}
```

Now, `divide` returns a Result type. You can unwrap it to see if it's a success or a failure:

```ts
const result = divide(10, 0)
  if (isSuccess(result)) {
    return "The result is ${result.value}"
  } else {
    return "Error: ${result.error}"
  }
```

## Extra data

The first argument to the `failure` function is always a string error message. You can have an optional second argument where you can pass in an object containing extra data.

```ts
def parseConfig(path: string): Result {
  return failure("Bad syntax in ${path}", { line: 4, column: 12 })
}
```

You can read the message with `.error` and the extra data with `.data`:

```ts
const result = parseConfig("app.json")
if (isFailure(result)) {
  print("${result.error} (line ${result.data.line})")
}
```

`.data` is an empty object when the producer passed none, so reading a field
off it gives null rather than an error.

## Unwrapping, continued

Or more idiomatically with pattern matching:

```ts
const result = divide(10, 0)
if (result is success(value)) {
  return "The result is ${value}"
} else {
  return "Error: ${result.error}"
}
```

Or:

```ts
const result = divide(10, 0)
return match (result) {
  success(value) => "The result is ${value}"
  failure(error) => "Error: ${error}"
}
```

## The `catch` keyword

You can use the `catch` keyword to specify a default value in case of failure.

```ts
node main(msg: string) {
  // `result` is a Result type
  const result = divide(10, 0)

  // `result2` is a number. If `divide` is a failure,
  // `result2` gets the default value of 3
  const result2 = divide(10, 0) catch 3
}
```

`catch` unwraps the Result type for you, and if it's a failure, uses the default value instead.

## The `?` operator

Often there is no sensible default. The function that got the failure wants to hand it to its own caller. Written out, that is four lines:

```ts
def readMeta(root: string): Result<MetaFile> {
  const parsed = readJsonFile(root, "meta.json")
  if (parsed is failure) {
    return parsed
  }
  return success(parsed.value)
}
```

The `?` operator is that check in one character. Put it after an expression that holds a `Result`. If the Result is a success, you get the value. If it is a failure, the enclosing function returns that failure right there:

```ts
def readMeta(root: string): Result<MetaFile> {
  const parsed = readJsonFile(root, "meta.json")?
  return success(parsed)
}
```

`?` binds tighter than anything else, so it chains:

```ts
const name = fetchUser(id)?.profile()?.displayName
```

Each `?` checks the Result before it, and the first failure ends the function.

Two rules follow from what `?` does. The function that uses it must be declared to return a `Result`, since a failure may come out of it. And the thing before `?` must be a `Result`; `?` on a plain value is a type error.

`?` passes the failure through unchanged. If you want to add a message, keep the Result and match on it:

```ts
const parsed = readJsonFile(root, "meta.json")
if (parsed is failure(error)) {
  return failure("meta.json could not be read: ${error}")
}
```

## The pipe operator (`|>`)
If can be a pain to unwrap the `Result` type all the time. Here is a function called `half`, which works if the number is even:

```ts
def half(x: number): Result {
  if (x % 2 != 0) {
    return failure("Number must be even to be halved, got ${x}")
  }
  return divide(x, 2)
}
```

Suppose I want to call `half` on some number three times. Unwrapping it each time as a pain:

```ts
  let result = half(10)
  if (isSuccess(result)) {
    result = half(result.value)
    if (isSuccess(result)) {
      result = half(result.value)
      return result
    }
  }
  return "Error: ${result.error}"
```

Use the pipe operator (`|>`) instead:

```ts
const result = success(10) |> half |> half |> half
```

Pipe is a way to chain function calls together. The return value of one function is passed as the parameter to the next function. Pipe works with `Result` types, and it short-circuits on failures. So if the return value of a function is a success, pipe will unwrap it and pass it to the next function in the chain. But if it's a failure, it will short-circuit the chain and return that failure.

Printing the `result` variable, we see that it's an error, with the error message:

```
Number must be even to be halved, got 5
```

The first call to `half` succeeded, but the second call failed, and so the pipe chain short-circuited and did not make the third call to `half`.

### Pipes and PFA

You can use PFA on functions in a pipe chain:

```ts
const result = success([10, 20, 30]) |> map.partial(func: half)
```

This only works if the resulting function has exactly one parameter left.

## The `try` keyword

Agency has a `try` keyword. It is unrelated to `catch`. Even though Agency doesn't throw errors, you might call some TypeScript code that throws an error. The `try` keyword will catch the error and convert it to a failure for you:

```ts
// result is now a Result type
// if foo() throws an error, result will be a failure
const result = try foo()
```

`try` gives you a `Result`, so `?` works after it. The `?` applies to the whole `try`: if the call throws, the failure is returned from your function right there.

```ts
def parseConfig(text: string): Result<Config> {
  const parsed = try JSON.parse(text)?
  return success(parsed)
}
```

Agency also adds an automatic try-catch around every function definition, and if an error is thrown, it returns a `Failure` type.

## Failure propagation

A failure is self-propagating. If you pass one to a function whose parameter is not typed to accept Results (`Result`, explicit `any`, or a union containing either), the function is skipped and the call returns the original failure, exactly like a pipe chain short-circuiting. Each skipped hop is recorded on the failure's `skippedFunctions` list, so the error you eventually inspect still points at the function that produced it. Passing a failure to an imported TypeScript function, or calling a method on a Result you forgot to unwrap, throws an error naming the producer instead.

```ts
def getReport(id: string): Result {
  return failure("HTTP 404: report not found")
}

def wordCount(text: string): number {
  return text.split(" ").length
}

node main() {
  const report = getReport("abc")   // oops: never checked
  const count = wordCount(report)   // wordCount is skipped
  // count IS the original failure: count.error is the 404 message, and
  // count.skippedFunctions is [{ name: "wordCount", param: "text" }].
}
```

## `Result` type parameters

The `Result` type has two type parameters: the success type and the failure's data type. The failure's message is always a string, so it is never named here. Both parameters default to `any`:

```ts
// success value is `any`, failure data is `any`
const result1: Result = divide(10, 0)

// success value is `number`, failure data is `any`
const result2: Result<number> = divide(10, 0)

// success value is `number`, failure data is a `ParseFailure`
const result3: Result<number, ParseFailure> = parseConfig("app.json")
```

The second parameter must be an object type. `Result<number, string>` is a compile error, because no `failure()` call can produce string data.

### A declared data type is required

If a function names a data type, every `failure` in it has to supply that data:

```ts
type ParseFailure = { line: number }

def loadConfig(path: string): Result<Config, ParseFailure> {
  // compile error: this function promised ParseFailure data
  return failure("No config at ${path}")
}
```

Otherwise the type says the data is there when it is not, and a caller reading `result.data.line` gets null with nothing to explain why.

To allow both forms in one function, add `null` to the data type:

```ts
def loadConfig(path: string): Result<Config, ParseFailure | null> {
  if (!exists(path)) {
    return failure("No config at ${path}")
  }
  return failure("Bad syntax in ${path}", { line: 4 })
}
```

At runtime `.data` is always an object, so `result.data == null` is never true. The `null` in `ParseFailure | null` says the producer may omit the data. To tell the two apart, test a field: `if (result.data.line != null)`.
