result: cleanups

* fix defect raising without `$`
* doc updates
* better `?` that doesn't cause raises effect (it should, because of
FieldError but...)
This commit is contained in:
Jacek Sieka 2020-04-11 16:22:37 +02:00
parent 5512e89d4c
commit 805ef4f1b2
No known key found for this signature in database
GPG Key ID: A1B09461ABB656B8
3 changed files with 27 additions and 14 deletions

View File

@ -7,7 +7,7 @@ description = "Backports, standard library candidates and small utilities that
license = "Apache License 2.0"
skipDirs = @["tests"]
requires "nim >= 0.19.0"
requires "nim >= 1.2.0"
task test, "Run all tests":
exec "nim c -r --threads:off tests/all_tests"

View File

@ -215,18 +215,17 @@ type
## `f(x)` will call the slow `genericReset` and zero-init `x` again,
## unnecessarily.
##
## Comparing `Result` performance to exceptions in Nim is difficult - the
## specific performance will depend on the error type used, the frequency
## at which exceptions happen, the amount of error handling code in the
## application and the compiler and backend used.
## Comparing `Result` performance to exceptions in Nim is difficult - it
## will depend on the error type used, the frequency at which exceptions
## happen, the amount of error handling code in the application and the
## compiler and backend used.
##
## * the default C backend in nim uses `setjmp` for exception handling -
## the relative performance of the happy path will depend on the structure
## of the code: how many exception handlers there are, how much unwinding
## happens. `setjmp` works by taking a snapshot of the full CPU state and
## saving it in memory when enterting a try block (or an implict try
## block, such as is introduced with `defer` and similar constructs) which
## is an expensive operation.
## saving it to memory when enterting a try block (or an implict try
## block, such as is introduced with `defer` and similar constructs).
## * an efficient exception handling mechanism (like the C++ backend or
## `nlvm`) will usually have a lower cost on the happy path because the
## value can be returned more efficiently. However, there is still a code
@ -236,9 +235,8 @@ type
## through a Result - at raise time, capturing a call stack and allocating
## memory for the Exception is expensive, so the performance difference
## comes down to the complexity of the error type used.
## * checking for errors with Result is local branching operation that
## happens on the happy path - even if all that is done is passing the
## error to the next layer - when errors happen rarely, this may be a cost.
## * checking for errors with Result is local branching operation that also
## happens on the happy path - this may be a cost.
##
## An accurate summary might be that Exceptions are at its most efficient
## when errors are not handled and don't happen.
@ -273,7 +271,7 @@ func raiseResultError[T, E](self: Result[T, E]) {.noreturn, noinline.} =
raise (res ResultError[E])(msg: "Trying to access value with err", error: self.e)
func raiseResultDefect(m: string, v: auto) {.noreturn, noinline.} =
if compiles($v): raise (ref ResultDefect)(msg: m & ": " & $v)
when compiles($v): raise (ref ResultDefect)(msg: m & ": " & $v)
else: raise (ref ResultDefect)(msg: m)
template checkOk(self: Result) =
@ -316,6 +314,11 @@ template isErr*(self: Result): bool = not self.o
func map*[T, E, A](
self: Result[T, E], f: proc(x: T): A): Result[A, E] {.inline.} =
## Transform value using f, or return error
##
## ```
## let r = Result[int, cstring).ok(42)
## assert r.map(proc (v: int): int = $v).get() == "42"
## ```
if self.isOk: result.ok(f(self.v))
else: result.err(self.e)
@ -570,6 +573,10 @@ template `?`*[T, E](self: Result[T, E]): T =
# TODO the v copy is here to prevent multiple evaluations of self - could
# probably avoid it with some fancy macro magic..
let v = (self)
if v.isErr: return err(typeof(result), v.error)
if v.isErr:
when typeof(result) is typeof(v):
return v
else:
return err(typeof(result), v.e)
v.value
v.v

View File

@ -173,8 +173,14 @@ func testQn2(): Result[int, string] =
# looks like we can even use it creatively like this
if ?fails() == 42: raise (ref ValueError)(msg: "shouldn't happen")
func testQn3(): Result[bool, string] =
# different T but same E
let x = ?works() - ?works()
result.ok(x == 0)
doAssert testQn()[] == 0
doAssert testQn2().isErr
doAssert testQn3()[]
type
AnEnum = enum