# 8. Errors

> Chapter 8: exceptions in Alice: raise, begin/rescue/else/ensure, method-level rescue, the rescue modifier, retry, custom exceptions, and when to use nil or a result type instead.

Canonical: https://alice-lang.org/documentation/learn/errors/

[Documentation index](https://alice-lang.org/documentation/llms.txt) · [Complete book](https://alice-lang.org/llms-full.txt)

Some failures can be predicted, and the last two chapters handled them with `nil` and result types. Others come from outside the program: a file is missing, the network is down, a customer orders a tea the shop does not have. You cannot check for those everywhere. **Exceptions** let one part of a program report a problem, and another part, maybe far away, decide what to do about it.

## 8.1 What an unhandled error looks like

Let's start by making something fail on purpose:

```alice
puts "Putting the kettle on."
raise InvalidOperationException.new("the kettle is empty")
puts "Tea is ready."
```

```text title="Runtime error"
Putting the kettle on.
error: InvalidOperationException: the kettle is empty
```

`raise` stops the normal flow. The rest of the program, here the last `puts`, does not run. Nothing handled the exception, so the program ends: Alice prints the type and the message and exits with status 101.

`raise "message"` with only a text raises a plain `Exception`. For anything a caller might want to handle on its own, pick a specific type. The common .NET ones are:

| Exception                              | Raised when                                     |
| -------------------------------------- | ----------------------------------------------- |
| `ArgumentException`                    | an argument is not acceptable                   |
| `InvalidOperationException`            | the object is not in a state where this works   |
| `FormatException`                      | text cannot be parsed, as by `Int32.Parse("x")` |
| `KeyNotFoundException`                 | a hash lookup misses                            |
| `IndexOutOfRangeException`             | an array index is outside the array             |
| `OverflowException`                    | integer arithmetic overflows                    |
| `IOException`, `FileNotFoundException` | a file operation fails                          |

They form a family tree. `ArgumentNullException` is a kind of `ArgumentException`, and every exception is a kind of `Exception`.

## 8.2 Rescuing an exception

`begin ... rescue ... end` runs code and catches exceptions of the types you name:

```alice
def cups_from(text : String) : Int32
  Int32.Parse(text)
end

begin
  puts cups_from("3")
  puts cups_from("three")
  puts "not reached"
rescue e : FormatException
  puts "could not read a number: #{e.message}"
end
puts "the shop stays open"
```

```text title="Output"
3
could not read a number: The input string 'three' was not in a correct format.
the shop stays open
```

When `cups_from("three")` raises, Alice leaves the `begin` block at once, finds the `rescue` that matches the type, and runs it. After the `end`, the program continues normally. `e` is the exception object, and `e.message` is its text.

### The full form

A `begin` block can have several parts:

```alice
stock = {"Assam" => 3}
["Assam", "Oolong"].each do |tea|
  begin
    n = stock[tea]
    puts "#{tea}: #{n} left"
  rescue KeyNotFoundException, ArgumentException => e
    puts "#{tea}: not stocked"
  else
    puts "lookup worked"
  ensure
    puts "done with #{tea}"
  end
end
```

```text title="Output"
Assam: 3 left
lookup worked
done with Assam
Oolong: not stocked
done with Oolong
```

- Each `rescue` lists one or more types. `rescue A, B => e` and `rescue e : A | B` mean the same thing. `rescue => e`, without a type, catches any `Exception`.
- `else` runs only when nothing was raised.
- `ensure` runs **always**, whether the block succeeded, raised, or was rescued. It is the place to close files and release locks.

### Rescue a whole method

When the whole body of a method is the `begin`, leave the `begin` out. The `rescue` and `ensure` then attach to the method:

```alice
def price_of(tea : String, menu : Hash(String, Int32)) : Int32
  menu[tea]
rescue KeyNotFoundException
  0
end

menu = {"Assam" => 250}
puts price_of("Assam", menu)
puts price_of("Oolong", menu)
```

```text title="Output"
250
0
```

A `do ... end` block may have `rescue` and `ensure` too, as the stock example above could have used.

### A value when something fails: the `rescue` modifier

For a one-line fallback, put `rescue` after an expression:

```alice
cups = (Int32.Parse("many") rescue 1)
puts cups
```

```text title="Output"
1
```

It is short, but it catches every exception, including ones you did not expect. Use it only around expressions that can fail in one obvious way.

## 8.3 Your own exceptions

Your program has its own kinds of failure. Give each one a class that inherits from `Exception`, so callers can rescue exactly that kind, and attach the data a handler needs:

```alice
class OutOfTea < Exception
  getter tea : String

  def initialize(@tea : String)
    super("we are out of #{tea}")
  end
end

def brew(tea : String)
  raise OutOfTea.new(tea) if tea == "Oolong"
  puts "brewing #{tea}"
end

begin
  brew("Assam")
  brew("Oolong")
rescue e : OutOfTea
  puts e.message
  puts "suggest something instead of #{e.tea}"
end
```

```text title="Output"
brewing Assam
we are out of Oolong
suggest something instead of Oolong
```

`super("...")` passes the message to `Exception`. Give your exception class an `initialize` that calls `super`: the parent's constructor is not inherited.

### Re-raising

Sometimes a method wants to notice an error, log it, and still let the caller deal with it. A bare `raise` inside `rescue` raises the same exception again:

```alice
def serve(tea : String)
  if tea == "Oolong"
    raise ArgumentException.new("unknown tea #{tea}")
  end
  puts "serving #{tea}"
rescue e : ArgumentException
  puts "log: #{e.message}"
  raise
end

begin
  serve("Oolong")
rescue e : ArgumentException
  puts "caller: sorry, we cannot serve that"
end
```

```text title="Output"
log: unknown tea Oolong
caller: sorry, we cannot serve that
```

### Trying again: `retry`

`retry` inside a `rescue` runs the `begin` block again from the top. It suits operations that may fail for a moment, like a network call. Always limit the number of tries:

```alice
attempts = 0
begin
  attempts += 1
  raise IOException.new("the line is busy") if attempts < 3
  puts "connected after #{attempts} attempts"
rescue e : IOException
  retry if attempts < 5
  puts "giving up"
end
```

```text title="Output"
connected after 3 attempts
```

## 8.4 Which tool for which failure

You now have three ways to express that something did not work. They are not interchangeable:

| Situation                                                                                | Use               | Why                                                     |
| ---------------------------------------------------------------------------------------- | ----------------- | ------------------------------------------------------- |
| The value may simply not exist: a search, an optional field                              | `T?`              | The caller checks with `if x`; the compiler enforces it |
| A failure the caller should handle each time, with a reason                              | a result sum type | The reason is in the type; `case` must handle it        |
| Something exceptional or outside the program: I/O, bad configuration, a broken invariant | an exception      | It travels up to a place that can do something sensible |

A good rule of thumb: if the caller will usually handle the failure right away, make it part of the return type. If most callers cannot do anything useful about it, raise.

## Project: unknown teas

In chapter 5, a tea that was not on the menu made `MENU[tea]` raise `KeyNotFoundException`, and the whole receipt failed. Now the shop raises its own exception with a clear message, and the receipt loop rescues it per line:

```alice
class UnknownTea < Exception
  def initialize(tea : String)
    super("no tea called #{tea}")
  end
end

MENU : Hash(String, Int32) = {"Assam" => 250, "Sencha" => 350}

def line_cents(tea : String, cups : Int32) : Int64
  raise UnknownTea.new(tea) unless MENU.key?(tea)
  raise ArgumentException.new("cups must be 1 to 20") unless cups.between?(1, 20)
  MENU[tea].to_i64 * cups
end

total = 0_i64
[{"Assam", 2}, {"Oolong", 1}, {"Sencha", 0}, {"Sencha", 1}].each do |line|
  begin
    total += line_cents(line[0], line[1])
  rescue e : UnknownTea
    puts "skipped: #{e.message}"
  rescue e : ArgumentException
    puts "skipped #{line[0]}: #{e.message}"
  end
end
puts "total: #{total}"
```

```text title="Output"
skipped: no tea called Oolong
skipped Sencha: cups must be 1 to 20
total: 850
```

:::caution\[Common mistakes]

- **`rescue => e` everywhere.** It also catches bugs, such as an `IndexOutOfRangeException`, and hides them. Rescue the specific types you expect.
- **Forgetting the constructor of a custom exception.** `raise MyError, "text"` needs an `initialize(message : String)` that calls `super(message)`.
- **Doing cleanup after the `begin` block instead of in `ensure`.** If an exception escapes, code after the block never runs.
- **`retry` without a limit.** A failure that does not go away makes the loop run forever.
  :::

## Exercises

1. Write `safe_div(a : Int32, b : Int32) : Int32?` that returns `nil` when `b` is 0, without using exceptions. Then write a version that rescues `DivideByZeroException` instead. Which one do you prefer, and why?
2. Write `read_age(text : String) : Int32` that raises `ArgumentException` with a helpful message when the text is not a number or is negative. Call it with `"7"`, `"seven"` and `"-1"`, rescuing and printing each message.
3. In the `begin`/`rescue`/`else`/`ensure` form, which parts run when the `begin` body raises an exception that no `rescue` matches?

<details>
<summary>Answers</summary>

1. The check is clearer: dividing by zero is expected here, and the `Int32?` return type tells every caller about it.

   ```alice
   def safe_div(a : Int32, b : Int32) : Int32?
     return nil if b == 0
     a / b
   end

   def rescue_div(a : Int32, b : Int32) : Int32
     a / b
   rescue DivideByZeroException
     0
   end

   puts safe_div(7, 0).nil?
   puts rescue_div(7, 0)
   ```

   ```text title="Output"
   true
   0
   ```

2. Answer:

   ```alice
   def read_age(text : String) : Int32
     n = text.to_i?
     if n
       raise ArgumentException.new("an age cannot be negative") if n < 0
       return n
     end
     raise ArgumentException.new("'#{text}' is not a number")
   end

   ["7", "seven", "-1"].each do |text|
     begin
       puts read_age(text)
     rescue e : ArgumentException
       puts e.message
     end
   end
   ```

   ```text title="Output"
   7
   'seven' is not a number
   an age cannot be negative
   ```

3. Only `ensure` runs. Then the exception continues up to the caller.

</details>

## Summary

- `raise` stops normal flow with an exception. An unhandled exception ends the program with its type and message.
- `begin ... rescue Type => e ... else ... ensure ... end` catches, reports success, and cleans up. Methods and `do` blocks can use `rescue`/`ensure` without `begin`.
- `expr rescue fallback` gives a value on any failure. Use it sparingly.
- Custom exceptions inherit from `Exception`, call `super(message)`, and can carry data.
- Bare `raise` re-raises; `retry` runs the block again.
- Prefer `T?` or a result type when the caller should always handle the failure; raise for the exceptional.

The next five chapters are about what makes Alice different from the languages it borrows from. [Chapter 9](https://alice-lang.org/documentation/learn/ownership.md) starts with memory.

## Continue reading

- Previous: [7. Sum types and pattern matching](https://alice-lang.org/documentation/learn/sum-types.md)
- Next: [9. Ownership and memory](https://alice-lang.org/documentation/learn/ownership.md)
