aboutsummaryrefslogtreecommitdiff
path: root/docs/ERRORS.md
blob: a6e7a6a2ce80da280cc26f5d91fa4a1e5d911f2e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# Error Handling

Puck's error handling is shamelessly stolen from Swift.
It uses a combination of Option/Result types and try/catch/finally statements, and leans somewhat on Puck's metaprogramming capabilities.

```puck
func get_debug[T](): T =
  let value: Option[T] = self.unsafe_get(413)
  try:
    let value = value!
  catch Exception(e)


try:
  ..
catch:
  ..
finally:
  print "No such errors"
```

There are several ways to handle errors in Puck. If the error is encoded in the type, one can:
1. `match` on the error
2. compactly match on the error with `if ... of`
3. propagate the error with `?`
4. throw the error with `!`

If an error is thrown, one must explicitly handle (or disregard) it with a `try/catch` block.
This method of error handling may feel more familiar to Java programmers.

## Errors as monads

Puck provides [`Option[T]`](std/default/options.pk) and a [`Result[T, E]`](std/default/results.pk) types, imported by default. These are `union` types and so must be pattern matched upon to be useful: but the standard library provides [a bevy of helper functions](std/default/results.pk).
Two in particular are of note. The `?` operator unwraps a Result or propagates its error up a function call (and may only be used in type-appropriate contexts). The `!` operator unwraps an Option or Result directly or throws an exception in the case of None or Error.

```puck
pub macro `?`[T, E](self: Result[T, E]) =
  quote:
    match `self`
    of Okay(x): x
    of Error(e): return Error(e)
```

```puck
pub func `!`[T](self: Option[T]): T =
  match self
  of Some(x): x
  of None: raise EmptyValue

pub func `!`[T, E](self: Result[T, E]): T =
  of Okay(x): x
  of Error(e): raise e
```

The utility of the provided helpers in [`std.options`](std/default/options.pk) and [`std.results`](std/default/results.pk) should not be understated. While encoding errors into the type system may appear restrictive at first glance, some syntactic sugar goes a long way in writing compact and idiomatic code. Java programmers in particular are urged to give type-first errors a try, before falling back on unwraps and `try`/`catch`.

A notable helpful type is the aliasing of `Result[T]` to `Result[T, ref Err]`, for when the particular error does not matter. This breaks `try`/`catch` exhaustion (as `ref Err` denotes a reference to *any* Error), but is particularly useful when used in conjunction with the propagation operator.

## Errors as catchable exceptions

Errors raised by `raise`/`throw` (or subsequently the `!` operator) must be explicitly caught and handled via a `try`/`catch`/`finally` statement.
If an exception is not handled within a function body, the function must be explicitly marked as a throwing function via the `yeet` prefix (name to be determined). The compiler will statically determine which exceptions in particular are thrown from any given function, and enforce them to be explicitly handled or explicitly ignored.

Errors are types. An error thrown from an unwrapped `Result[T, E]` is of type `E`. `catch` statements, then, may pattern match upon possible errors, behaving similarly to `of` branches.

```puck
try:
  ...
catch "Error":
  ...
finally:
  ...
```

This creates a distinction between two types of error handling, working in sync: functional error handling with [Option](https://en.wikipedia.org/wiki/Option_type) and [Result](https://en.wikipedia.org/wiki/Result_type) types, and object-oriented error handling with [catchable exceptions](https://en.wikipedia.org/wiki/Exception_handling). These styles may be swapped between with minimal syntax overhead. Libraries, however, should universally use `Option`/`Result`, as this provides the best support for both styles.

<!-- [nullable types](https://en.wikipedia.org/wiki/Nullable_type)?? -->

## Unrecoverable exceptions

There exist errors from which a program can not reasonably recover. These are the following:
- `Assertation Failure`: a call to an `assert` function has returned false at runtime.
- `Out of Memory`: the executable is out of memory.
- `Stack Overflow`: the executable has overflowed the stack.
- any others?

They are not recoverable, but the user should be aware of them as possible failure conditions.

References: [Error Handling in Swift](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/errorhandling)