Skip to content

Commit

Permalink
1.6 updates (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
arnetheduck authored Nov 8, 2023
1 parent 52a1e1f commit 47a89e4
Show file tree
Hide file tree
Showing 6 changed files with 18 additions and 27 deletions.
4 changes: 4 additions & 0 deletions src/00_introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ When in doubt:

The latest version of this book can be found [online](https://status-im.github.io/nim-style-guide/) or on [GitHub](https://github.com/status-im/nim-style-guide/).

This guide currently targets Nim v1.6.

At the time of writing, v2.0 has been released but its new garbage collector is not yet stable enough for production use. It is advisable to test new code with both `--mm:gc` and `--mm:orc` (the default) in the transition period.

<!-- toc -->

## Practical notes
Expand Down
26 changes: 7 additions & 19 deletions src/errors.exceptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,17 @@ In general, prefer [explicit error handling mechanisms](errors.result.md).

Annotate each module at top-level (before imports):

* `{.push raises: [Defect].}` (nim 1.2)
* `{.push raises: [].}` (nim 1.4+)

To make a module compatible with both Nim 1.2 and newer versions, use:

```nim
when (NimMajor, NimMinor) < (1, 4):
{.push raises: [Defect].}
else:
{.push raises: [].}
```


Use explicit `{.raises.}` annotation for each public (`*`) function.

Raise `Defect` to signal panics and situations that the code is not prepared to handle.
Raise `Defect` to signal panics and undefined behavior that the code is not prepared to handle.

```nim
# Enable exception tracking for all functions in this module
`{.push raises: [Defect].}` # Always at start of module
`{.push raises: [].}` # Always at start of module
# Inherit from CatchableError and name XxxError
type MyLibraryError = object of CatchableError
Expand All @@ -37,7 +28,7 @@ type MySpecificError = object of MyLibraryError
# Explicitly annotate functions with raises - this replaces the more strict
# module-level push declaration on top
func f() {.raises: [Defect, MySpecificError]} = discard
func f() {.raises: [MySpecificError]} = discard
# Isolate code that may generate exceptions using expression-based try:
let x =
Expand Down Expand Up @@ -68,16 +59,15 @@ raise (ref MyError)(msg: "description", data: value)
* Nim exception hierarchy unclear and changes between versions
* The distinction between `Exception`, `CatchableError` and `Defect` is inconsistently implemented
* [Exception hierarchy RFC not being implemented](https://github.com/nim-lang/Nim/issues/11776)
* `Defect` is [not correctly tracked]((https://github.com/nim-lang/Nim/issues/12862))
* Nim 1.4 further weakens compiler analysis around `Defect`(https://github.com/nim-lang/Nim/pull/13626)
* `Defect` is [not tracked](https://github.com/nim-lang/Nim/pull/13626)
* Without translation, exceptions leak information between abstraction layers
* Writing exception-safe code in Nim impractical due to missing critical features present in C++
* No RAII - resources often leak in the presence of exceptions
* Destructors incomplete / unstable and thus not usable for safe EH
* No constructors, thus no way to force particular object states at construction
* `ref` types incompatible with destructors, even if they worked
* Poor performance of error path
* Several heap allocations for each Exception (exception, stack trace, string)
* Several heap allocations for each `Exception`` (exception, stack trace, message)
* Expensive stack trace
* Poor performance on happy path
* Every `try` and `defer` has significant performance overhead due to `setjmp` exception handling implementation
Expand All @@ -86,19 +76,17 @@ raise (ref MyError)(msg: "description", data: value)

The use of exceptions in Nim has significantly contributed to resource leaks, deadlocks and other difficult bugs. The various exception handling proposals aim to alleviate some of the issues but have not found sufficient grounding in the Nim community to warrant the language changes necessary to proceed.

A notable exception to the guideline is `chronos` and `async`/`await` transformations that lack support for propagating checked exception information. Several bugs and implementation issues exist in the exception handling transformation employed by `async`.

### `Defect`

`Defect` does [not cause](https://github.com/nim-lang/Nim/issues/12862) a `raises` effect - code must be manually verified - common sources of Defect include:
`Defect` does [not cause](https://github.com/nim-lang/Nim/issues/12862) a `raises` effect - code must be manually verified - common sources of `Defect` include:

* Over/underflows in signed arithmetic
* `[]` operator for indexing arrays/seqs/etc (but not tables!)
* accidental/implicit conversions to `range` types

### `CatchableError`

Catching `CatchableError` implies that any errors are funnelled through the same exception handler. When called code starts raising new exceptions, it becomes difficult to find affected code - catching more specific errors avoids this maintenance problem.
Catching `CatchableError` implies that all errors are funnelled through the same exception handler. When called code starts raising new exceptions, it becomes difficult to find affected code - catching more specific errors avoids this maintenance problem.

Frameworks may catch `CatchableError` to forward exceptions through layers. Doing so leads to type erasure of the actual raised exception type in `raises` tracking.

Expand Down
2 changes: 1 addition & 1 deletion src/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ When porting code, there are two approaches:

* Bottom up - fix the underlying library / code
* Top down - isolate the legacy code with `try/except`
* In this case, we note where the Exception effect is coming from, should it be fixed in the future
* In this case, we note where the `Exception` effect is coming from, should it be fixed in the future
4 changes: 2 additions & 2 deletions src/errors.result.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Isolate legacy code with explicit exception handling, converting the errors to `

```nim
# Enable exception tracking for all functions in this module
{.push raises: [Defect].} # Always at start of module
{.push raises: [].} # Always at start of module
import stew/results
export results # Re-export modules used in public symbols
Expand All @@ -24,7 +24,7 @@ export results # Re-export modules used in public symbols
func f*(): Result[void, cstring]
# In special cases that warrant the use of exceptions, list these explicitly using the `raises` pragma.
func parse(): Type {.raises: [Defect, ParseError]}
func parse(): Type {.raises: [ParseError]}
```

See also [Result](libraries.results.md) for more recommendations about `Result`.
Expand Down
2 changes: 1 addition & 1 deletion src/language.memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Use heap allocation in glue layers.

Avoid `alloca`.

```
```nim
func init(T: type Yyy, a, b: int): T = ...
# Heap allocation as a local decision
Expand Down
7 changes: 3 additions & 4 deletions src/language.proctypes.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
## Callbacks, closures and forward declarations `[language.proctypes]`

Annotate `proc` type definitions and forward declarations with `{.raises [Defect], gcsafe.}` or specific exception types.
Annotate `proc` type definitions and forward declarations with `{.raises [], gcsafe.}` or specific exception types.

```nim
# By default, Nim assumes closures may raise any exception and are not gcsafe
# By annotating the callback with raises and gcsafe, the compiler ensures that
# any functions assigned to the closure fit the given constraints
type Callback = proc(...) {.raises: [Defect], gcsafe.}
type Callback = proc(...) {.raises: [], gcsafe.}
```

### Practical notes

* Without annotations, `raises Exception` and no GC-safety is assumed by the compiler, infecting deduction in the whole call stack
* Without annotations, `{.raises [Exception].}` and no GC-safety is assumed by the compiler, infecting deduction in the whole call stack
* Annotations constrain the functions being assigned to the callback to follow its declaration, simplifying calling the callback safely
* In particular, callbacks are difficult to reason about when they raise exceptions - what should the caller of the callback do?

0 comments on commit 47a89e4

Please sign in to comment.