|
| 1 | +## The `errors` pragma |
| 2 | + |
| 3 | +The `errors` pragma is similar to the `raises` pragma, but it uses |
| 4 | +the Nim type system to ensure that the raised recoverable errors |
| 5 | +will be handled at the call-sites of the annotated function. |
| 6 | + |
| 7 | +To achieve this, it performs the following simple transformation: |
| 8 | + |
| 9 | +```nim |
| 10 | +proc foo(x: int, y: string): float {.errors: (ValueError, KeyError).} = |
| 11 | + body |
| 12 | +``` |
| 13 | + |
| 14 | +is re-written to the equivalent of: |
| 15 | + |
| 16 | +```nim |
| 17 | +type |
| 18 | + Raising[ErrorsList, ResultType] = distinct ResultType |
| 19 | +
|
| 20 | +proc foo_original(x: int, y: string): float {. |
| 21 | + raises: [Defect, ValueError, KeyError] |
| 22 | +.} = |
| 23 | + body |
| 24 | +
|
| 25 | +template foo(x: int, y: string): untyped = |
| 26 | + Raising[(ValueError, KeyError), float](foo_original(x, y)) |
| 27 | +``` |
| 28 | + |
| 29 | +Please note that the original proc now features a `raises` annotation |
| 30 | +that will guarantee that no other exceptions might be raised from it. |
| 31 | +The `Defect` type was implicitly added to the list as a convenience. |
| 32 | + |
| 33 | +The returned distinct type will be useless at the call-site unless |
| 34 | +it is stripped-away through `raising`, `either` or `check` which are |
| 35 | +the error-handling mechanisms provided by this library and discussed |
| 36 | +further in this document. |
| 37 | + |
| 38 | +If you accidentally forget to use one of the error-handling mechanisms, |
| 39 | +you'll get a compilation error along these lines: |
| 40 | + |
| 41 | +``` |
| 42 | +required type for x: float |
| 43 | + but expression 'Raising[(ValueError, KeyError), float](foo_original(x, y))' is of type: Raising[tuple of (ValueError, KeyError), system.float] |
| 44 | +``` |
| 45 | + |
| 46 | +Please note that if you have assigned the `Raising` result to a |
| 47 | +variable, the compilation error might happen on a line where you |
| 48 | +attempt to use that variable. To fix the error, please introduce |
| 49 | +error handling as early as possible at the right call-site such |
| 50 | +that no `Raising` variable is created at all. |
| 51 | + |
| 52 | +`noerrors` is another pragma provided for convenience which is |
| 53 | +equivalent to an empty `errors` pragma. The forced error handling |
| 54 | +through the `Raising` type won't be applied. |
| 55 | + |
| 56 | +Both pragmas can be combined with the `nodefects` pragma that |
| 57 | +indicates that the specific proc should be proven to be Defect-free. |
| 58 | + |
| 59 | +The transformation uses a template by default to promote efficiency, |
| 60 | +but if you need to take the address the Raising proc, please add the |
| 61 | +`addressable` pragma that will force the wrapper to be a regular proc. |
| 62 | + |
| 63 | +Finally, `failing` is another pragma provided for convenience which |
| 64 | +is equivalent to `{.errors: (CatchableError).}`. |
| 65 | + |
| 66 | +## The `raising` annotation |
| 67 | + |
| 68 | +The `raising` annotation is the simplest form of error tracking |
| 69 | +similar to the `try` annotation made popular by the [Midori error model](http://joeduffyblog.com/2016/02/07/the-error-model/), |
| 70 | +which is also [proposed for addition in C++](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0709r0.pdf) and [already available in Zig](https://ziglang.org/documentation/master/#try). |
| 71 | + |
| 72 | +It merely marks the locations in the code where errors might be raised |
| 73 | +and strips away the `Raising` type to disarm the compiler checks: |
| 74 | + |
| 75 | +```nim |
| 76 | +proc attachValidator(node: BeaconNode, keyFile: string) {.failing.} = |
| 77 | + node.addValidator(raising ValidatorPrivKey.init(raising readFile(keyFile)) |
| 78 | +``` |
| 79 | + |
| 80 | +When applied to a `Result` or an `Option`, `raising` will use the |
| 81 | +`tryGet` API to attempt to obtain the computed value. |
| 82 | + |
| 83 | +## The capital `Try` API |
| 84 | + |
| 85 | +The capital `Try` API is similar to a regular `try` expression |
| 86 | +or a `try` statement. The only difference is that you must provide |
| 87 | +exception handlers for all possible recoverable errors. If you fail |
| 88 | +to do so, the compiler will point out the line in the `try` block |
| 89 | +where an unhandled exception might be raised: |
| 90 | + |
| 91 | +```nim |
| 92 | +proc replaceValues(values: var openarray[string], |
| 93 | + replacements: Table[MyEnum, string]) = |
| 94 | + Try: |
| 95 | + for v in mitems(values): |
| 96 | + let |
| 97 | + enumValue = parseEnum v |
| 98 | + replacement = replacements[enumValue] # Error here |
| 99 | + v = replacement |
| 100 | + except ValueError: |
| 101 | + echo "Invalid enum value" |
| 102 | +``` |
| 103 | + |
| 104 | +The above example will fail to compile with an error indicating |
| 105 | +that `replacements[enumValue]` may fail with an unhandled `KeyError`. |
| 106 | + |
| 107 | +## The `either` expression |
| 108 | + |
| 109 | +The `either` expression can be used with APIs based on `Option[T]`, |
| 110 | +`Result[T, E]` or the `errors` pragma when it's appropriate to |
| 111 | +discriminate only between successful execution and any type of |
| 112 | +failure. Regardless of the error handling scheme being used, |
| 113 | +`either` is used like this: |
| 114 | + |
| 115 | +```nim |
| 116 | +let x = either(foo(), fallbackValue) |
| 117 | +``` |
| 118 | + |
| 119 | +On success, `either` returns the successfully computed value |
| 120 | +and the failure side of the expression won't be evaluated at all. |
| 121 | + |
| 122 | +Besides providing a substitute value, the failure side of the |
| 123 | +expression may also feature a `noReturn` statement such as |
| 124 | +`return`, `raise`, `quit` as long at it's used with the following |
| 125 | +syntax: |
| 126 | + |
| 127 | +```nim |
| 128 | +let x = either foo(): |
| 129 | + return |
| 130 | +``` |
| 131 | + |
| 132 | +Within the failure path, you can also use the `error` keyword to |
| 133 | +refer to the raised exception or the `error` value of the failed |
| 134 | +`Result`. |
| 135 | + |
| 136 | +## The `check` expression |
| 137 | + |
| 138 | +The `check` macro provides a general mechanism for handling |
| 139 | +the failures of APIs based the `errors` pragma or `Result[T, E]` |
| 140 | +where `E` is an `enum` or a case object type. |
| 141 | + |
| 142 | +It takes an expression that might fail in multiple ways together |
| 143 | +with a block of error handlers that will be executed in case of |
| 144 | +failure. If the user failed to cover any of the possible failure |
| 145 | +types, this will result in a compilation error. |
| 146 | + |
| 147 | +On success, the `check` returns the successfully computed value |
| 148 | +of the checked expression. In case of failure, the appropriate |
| 149 | +error handler is executed. It may produce a substitute value or |
| 150 | +it may return from the current function with a `return`, `raise`, |
| 151 | +`quit` or any other `noReturn` API. |
| 152 | + |
| 153 | +The syntax of the `check` expression is the following: |
| 154 | + |
| 155 | +```nim |
| 156 | +let x = check foo(): |
| 157 | + SomeError as err: defaultValue |
| 158 | + AnotherError: return |
| 159 | + _: raise |
| 160 | +``` |
| 161 | + |
| 162 | +If the `foo()` function was using the `errors` pragma, the |
| 163 | +above example will be re-written to: |
| 164 | + |
| 165 | +```nim |
| 166 | +let x = try: |
| 167 | + raising foo() |
| 168 | +except SomeError as err: |
| 169 | + defaultValue |
| 170 | +except AnotherError: |
| 171 | + return |
| 172 | +except CatchableError: |
| 173 | + raise |
| 174 | +``` |
| 175 | + |
| 176 | +Alternatively, if `foo()` was returning a `Result[T, E: enum]`, the |
| 177 | +example will be re-written to: |
| 178 | + |
| 179 | +```nim |
| 180 | +let x = foo() |
| 181 | +if x.isOk: |
| 182 | + x.get |
| 183 | +else: |
| 184 | + case x.orror: |
| 185 | + of SomeError: |
| 186 | + let err = x.error |
| 187 | + defaultValue |
| 188 | + of AnotherError: |
| 189 | + return |
| 190 | + else: |
| 191 | + raiseResultError x |
| 192 | +``` |
| 193 | + |
| 194 | +The `Result` error type can also be a case object with a single `enum` |
| 195 | +discriminator that will be considered the error type. The generated code |
| 196 | +will be quite similar to the example above. |
| 197 | + |
| 198 | +Please note that the special default case `_` is considered equivalent |
| 199 | +to `CatchableError` or `else` when working with enums. |
| 200 | + |
0 commit comments