diff --git a/README.md b/README.md index 031e8ff..148ebf4 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ respective folders - `leb128` - utilities for working with LEB128-based formats (such as the varint style found in protobuf) - `objects` - get an object's base type at runtime, as a string - `ptrops` - pointer arithmetic utilities -- `result` - friendly, exception-free value-or-error returns, similar to `Option[T]`, from [nim-result](https://github.com/arnetheduck/nim-result/) +- `result` - moved to [nim-results](https://github.com/arnetheduck/nim-results/) - `shims` - backports of nim `devel` code to the stable version that Status is using - `sequtils2` - extensions to the `sequtils` module for working conveniently with `seq` diff --git a/stew.nimble b/stew.nimble index eb6feeb..924de70 100644 --- a/stew.nimble +++ b/stew.nimble @@ -8,6 +8,7 @@ license = "MIT or Apache License 2.0" skipDirs = @["tests"] requires "nim >= 1.2.0", + "results", "unittest2" let nimc = getEnv("NIMC", "nim") # Which nim compiler to use diff --git a/stew/results.nim b/stew/results.nim index 44a7f1e..a5c550b 100644 --- a/stew/results.nim +++ b/stew/results.nim @@ -1,1251 +1,9 @@ -# nim-result is also available stand-alone from https://github.com/arnetheduck/nim-result/ +# This module has graduated from stew and is now available from the +# `results` nimble package instead (https://github.com/arnetheduck/nim-results) -# Copyright (c) 2019 Jacek Sieka -# Licensed and distributed under either of -# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). -# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0). -# at your option. This file may not be copied, modified, or distributed except according to those terms. +when defined(stewWarnResults): + # This deprecation notice will be made default in some future stew commit + {.deprecated: "`stew/results` is now availabe as `import results` via the `results` Nimble package".} -type - ResultError*[E] = object of ValueError - ## Error raised when using `tryValue` value of result when error is set - ## See also Exception bridge mode - error*: E - - ResultDefect* = object of Defect - ## Defect raised when accessing value when error is set and vice versa - ## See also Exception bridge mode - - Result*[T, E] = object - ## Result type that can hold either a value or an error, but not both - ## - ## # Example - ## - ## ``` - ## import stew/results - ## - ## # Re-export `results` so that API is always available to users of your module! - ## export results - ## - ## # It's convenient to create an alias - most likely, you'll do just fine - ## # with strings or cstrings as error for a start - ## - ## type R = Result[int, string] - ## - ## # Once you have a type, use `ok` and `err`: - ## - ## func works(): R = - ## # ok says it went... ok! - ## R.ok 42 - ## func fails(): R = - ## # or type it like this, to not repeat the type: - ## result.err "bad luck" - ## - ## func alsoWorks(): R = - ## # or just use the shortcut - auto-deduced from the return type! - ## ok(24) - ## - ## if (let w = works(); w.isOk): - ## echo w[], " or use value: ", w.value - ## - ## # In case you think your callers want to differentiate between errors: - ## type - ## Error = enum - ## a, b, c - ## type RE[T] = Result[T, Error] - ## - ## # You can use the question mark operator to pass errors up the call stack - ## func f(): R = - ## let x = ?works() - ?fails() - ## assert false, "will never reach" - ## - ## # If you provide this exception converter, this exception will be raised on - ## # `tryValue`: - ## func toException(v: Error): ref CatchableError = (ref CatchableError)(msg: $v) - ## try: - ## RE[int].err(a).tryValue() - ## except CatchableError: - ## echo "in here!" - ## - ## # You can use `Opt[T]` as a replacement for `Option` = `Opt` is an alias for - ## # `Result[T, void]`, meaning you can use the full `Result` API on it: - ## let x = Opt[int].ok(42) - ## echo x.get() - ## - ## # ... or `Result[void, E]` as a replacement for `bool`, providing extra error - ## # information! - ## let y = Result[void, string].err("computation failed") - ## echo y.error() - ## - ## ``` - ## - ## See the tests for more practical examples, specially when working with - ## back and forth with the exception world! - ## - ## # Potential benefits: - ## - ## * Handling errors becomes explicit and mandatory at the call site - - ## goodbye "out of sight, out of mind" - ## * Errors are a visible part of the API - when they change, so must the - ## calling code and compiler will point this out - nice! - ## * Errors are a visible part of the API - your fellow programmer is - ## reminded that things actually can go wrong - ## * Jives well with Nim `discard` - ## * Jives well with the new Defect exception hierarchy, where defects - ## are raised for unrecoverable errors and the rest of the API uses - ## results - ## * Error and value return have similar performance characteristics - ## * Caller can choose to turn them into exceptions at low cost - flexible - ## for libraries! - ## * Mostly relies on simple Nim features - though this library is no - ## exception in that compiler bugs were discovered writing it :) - ## - ## # Potential costs: - ## - ## * Handling errors becomes explicit and mandatory - if you'd rather ignore - ## them or just pass them to some catch-all, this is noise - ## * When composing operations, value must be lifted before processing, - ## adding potential verbosity / noise (fancy macro, anyone?) - ## * There's no call stack captured by default (see also `catch` and - ## `capture`) - ## * The extra branching may be more expensive for the non-error path - ## (though this can be minimized with PGO) - ## - ## The API visibility issue of exceptions can also be solved with - ## `{.raises.}` annotations - as of now, the compiler doesn't remind - ## you to do so, even though it knows what the right annotation should be. - ## `{.raises.}` does not participate in generic typing, making it just as - ## verbose but less flexible in some ways, if you want to type it out. - ## - ## Many system languages make a distinction between errors you want to - ## handle and those that are simply bugs or unrealistic to deal with.. - ## handling the latter will often involve aborting or crashing the funcess - - ## reliable systems like Erlang will try to relaunch it. - ## - ## On the flip side we have dynamic languages like python where there's - ## nothing exceptional about exceptions (hello StopIterator). Python is - ## rarely used to build reliable systems - its strengths lie elsewhere. - ## - ## # Exception bridge mode - ## - ## When the error of a `Result` is an `Exception`, or a `toException` helper - ## is present for your error type, the "Exception bridge mode" is - ## enabled and instead of raising `ResultError`, `tryValue` will raise the - ## given `Exception` on access. `[]` and `value` will continue to raise a - ## `Defect`. - ## - ## This is an experimental feature that may be removed. - ## - ## # Other languages - ## - ## Result-style error handling seems pretty popular lately, specially with - ## statically typed languages: - ## Haskell: https://hackage.haskell.org/package/base-4.11.1.0/docs/Data-Either.html - ## Rust: https://doc.rust-lang.org/std/result/enum.Result.html - ## Modern C++: https://en.cppreference.com/w/cpp/utility/expected - ## More C++: https://github.com/ned14/outcome - ## - ## Swift is interesting in that it uses a non-exception implementation but - ## calls errors exceptions and has lots of syntactic sugar to make them feel - ## that way by implicitly passing them up the call chain - with a mandatory - ## annotation that function may throw: - ## https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/ErrorHandling.html - ## - ## # Considerations for the error type - ## - ## * Use a `string` or a `cstring` if you want to provide a diagnostic for - ## the caller without an expectation that they will differentiate between - ## different errors. Callers should never parse the given string! - ## * Use an `enum` to provide in-depth errors where the caller is expected - ## to have different logic for different errors - ## * Use a complex type to include error-specific meta-data - or make the - ## meta-data collection a visible part of your API in another way - this - ## way it remains discoverable by the caller! - ## - ## A natural "error API" progression is starting with `Opt[T]`, then - ## `Result[T, cstring]`, `Result[T, enum]` and `Result[T, object]` in - ## escalating order of complexity. - ## - ## # Result equivalences with other types - ## - ## Result allows tightly controlling the amount of information that a - ## function gives to the caller: - ## - ## ## `Result[void, void] == bool` - ## - ## Neither value nor error information, it either worked or didn't. Most - ## often used for `proc`:s with side effects. - ## - ## ## `Result[T, void] == Option[T]` - ## - ## Return value if it worked, else tell the caller it failed. Most often - ## used for simple computations. - ## - ## Works as a replacement for `Option[T]` (aliased as `Opt[T]`) - ## - ## ## `Result[T, E]` - - ## - ## Return value if it worked, or a statically known piece of information - ## when it didn't - most often used when a function can fail in more than - ## one way - E is typically a `string` or an `enum`. - ## - ## ## `Result[T, ref E]` - ## - ## Returning a `ref E` allows introducing dynamically typed error - ## information, similar to exceptions. - ## - ## # Other implemenations in nim - ## - ## There are other implementations in nim that you might prefer: - ## * Either from nimfp: https://github.com/vegansk/nimfp/blob/master/src/fp/either.nim - ## * result_type: https://github.com/kapralos/result_type/ - ## - ## `Option` compatibility - ## - ## `Result[T, void]` is similar to `Option[T]`, except it can be used with - ## all `Result` operators and helpers. - ## - ## One difference is `Option[ref|ptr T]` which disallows `nil` - `Opt[T]` - ## allows an "ok" result to hold `nil` - this can be useful when `nil` is - ## a valid outcome of a function, but increases complexity for the caller. - ## - ## # Implementation notes - ## - ## This implementation is mostly based on the one in rust. Compared to it, - ## there are a few differences - if know of creative ways to improve things, - ## I'm all ears. - ## - ## * Rust has the enum variants which lend themselves to nice construction - ## where the full Result type isn't needed: `Err("some error")` doesn't - ## need to know value type - maybe some creative converter or something - ## can deal with this? - ## * Nim templates allow us to fail fast without extra effort, meaning the - ## other side of `and`/`or` isn't evaluated unless necessary - nice! - ## * Rust uses From traits to deal with result translation as the result - ## travels up the call stack - needs more tinkering - some implicit - ## conversions would be nice here - ## * Pattern matching in rust allows convenient extraction of value or error - ## in one go. - ## - ## # Performance considerations - ## - ## When returning a Result instead of a simple value, there are a few things - ## to take into consideration - in general, we are returning more - ## information directly to the caller which has an associated cost. - ## - ## Result is a value type, thus its performance characteristics - ## generally follow the performance of copying the value or error that - ## it stores. `Result` would benefit greatly from "move" support in the - ## language. - ## - ## In many cases, these performance costs are negligeable, but nonetheless - ## they are important to be aware of, to structure your code in an efficient - ## manner: - ## - ## * Memory overhead - ## Result is stored in memory as a union with a `bool` discriminator - - ## alignment makes it somewhat tricky to give an exact size, but in - ## general, `Result[int, int]` will take up `2*sizeof(int)` bytes: - ## 1 `int` for the discriminator and padding, 1 `int` for either the value - ## or the error. The additional size means that returning may take up more - ## registers or spill onto the stack. - ## * Loss of RVO - ## Nim does return-value-optimization by rewriting `proc f(): X` into - ## `proc f(result: var X)` - in an expression like `let x = f()`, this - ## allows it to avoid a copy from the "temporary" return value to `x` - - ## when using Result, this copy currently happens always because you need - ## to fetch the value from the Result in a second step: `let x = f().value` - ## * Extra copies - ## To avoid spurious evaluation of expressions in templates, we use a - ## temporary variable sometimes - this means an unnecessary copy for some - ## types. - ## * Bad codegen - ## When doing RVO, Nim generates poor and slow code: it uses a construct - ## called `genericReset` that will zero-initialize a value using dynamic - ## RTTI - a process that the C compiler subsequently is unable to - ## optimize. This applies to all types, but is exacerbated with Result - ## because of its bigger footprint - this should be fixed in compiler. - ## * Double zero-initialization bug - ## Nim has an initialization bug that causes additional poor performance: - ## `var x = f()` will be expanded into `var x; zeroInit(x); f(x)` where - ## `f(x)` will call the slow `genericReset` and zero-init `x` again, - ## unnecessarily. - ## - ## 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 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 - ## and data size increase depending on the specific situation, as well as - ## loss of optimization opportunities to consider. - ## * raising an exception is usually (a lot) slower than returning an error - ## 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 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. - ## - ## # Relevant nim bugs - ## - ## https://github.com/nim-lang/Nim/issues/13799 - type issues - ## https://github.com/nim-lang/Nim/issues/8745 - genericReset slow - ## https://github.com/nim-lang/Nim/issues/13879 - double-zero-init slow - ## https://github.com/nim-lang/Nim/issues/14318 - generic error raises pragma - - # TODO https://github.com/nim-lang/Nim/issues/20699 - # case oResultPrivate: bool - # of false: - # eResultPrivate: E - # of true: - # vResultPrivate: T - - # TODO ResultPrivate works around - # * https://github.com/nim-lang/Nim/issues/3770 - # * https://github.com/nim-lang/Nim/issues/20900 - # - # Do not use these fields directly in your code, they're not meant to be - # public! - when T is void: - when E is void: - oResultPrivate*: bool - else: - case oResultPrivate*: bool - of false: - eResultPrivate*: E - of true: - discard - else: - when E is void: - case oResultPrivate*: bool - of false: - discard - of true: - vResultPrivate*: T - else: - case oResultPrivate*: bool - of false: - eResultPrivate*: E - of true: - vResultPrivate*: T - - Opt*[T] = Result[T, void] - -func raiseResultOk[T, E](self: Result[T, E]) {.noreturn, noinline.} = - # noinline because raising should take as little space as possible at call - # site - when T is void: - raise (ref ResultError[void])(msg: "Trying to access error with value") - else: - raise (ref ResultError[T])(msg: "Trying to access error with value", error: self.vResultPrivate) - -func raiseResultError[T, E](self: Result[T, E]) {.noreturn, noinline.} = - # noinline because raising should take as little space as possible at call - # site - mixin toException - - when E is ref Exception: - if self.eResultPrivate.isNil: # for example Result.default()! - raise (ref ResultError[void])(msg: "Trying to access value with err (nil)") - raise self.eResultPrivate - elif E is void: - raise (ref ResultError[void])(msg: "Trying to access value with err") - elif compiles(toException(self.eResultPrivate)): - raise toException(self.eResultPrivate) - elif compiles($self.eResultPrivate): - raise (ref ResultError[E])( - error: self.eResultPrivate, msg: $self.eResultPrivate) - else: - raise (ref ResultError[E])(msg: "Trying to access value with err", error: self.eResultPrivate) - -func raiseResultDefect(m: string, v: auto) {.noreturn, noinline.} = - mixin `$` - when compiles($v): raise (ref ResultDefect)(msg: m & ": " & $v) - else: raise (ref ResultDefect)(msg: m) - -func raiseResultDefect(m: string) {.noreturn, noinline.} = - raise (ref ResultDefect)(msg: m) - -template withAssertOk(self: Result, body: untyped): untyped = - # Careful - `self` evaluated multiple times, which is fine in all current uses - case self.oResultPrivate - of false: - when self.E isnot void: - raiseResultDefect("Trying to access value with err Result", self.eResultPrivate) - else: - raiseResultDefect("Trying to access value with err Result") - of true: - body - -template ok*[T: not void, E](R: type Result[T, E], x: untyped): R = - ## Initialize a result with a success and value - ## Example: `Result[int, string].ok(42)` - R(oResultPrivate: true, vResultPrivate: x) - -template ok*[E](R: type Result[void, E]): R = - ## Initialize a result with a success and value - ## Example: `Result[void, string].ok()` - R(oResultPrivate: true) - -template ok*[T: not void, E](self: var Result[T, E], x: untyped) = - ## Set the result to success and update value - ## Example: `result.ok(42)` - self = ok(type self, x) - -template ok*[E](self: var Result[void, E]) = - ## Set the result to success and update value - ## Example: `result.ok()` - self = (type self).ok() - -template err*[T; E: not void](R: type Result[T, E], x: untyped): R = - ## Initialize the result to an error - ## Example: `Result[int, string].err("uh-oh")` - R(oResultPrivate: false, eResultPrivate: x) - -template err*[T](R: type Result[T, cstring], x: string): R = - ## Initialize the result to an error - ## Example: `Result[int, string].err("uh-oh")` - const s = x # avoid dangling cstring pointers - R(oResultPrivate: false, eResultPrivate: cstring(s)) - -template err*[T](R: type Result[T, void]): R = - ## Initialize the result to an error - ## Example: `Result[int, void].err()` - R(oResultPrivate: false) - -template err*[T; E: not void](self: var Result[T, E], x: untyped) = - ## Set the result as an error - ## Example: `result.err("uh-oh")` - self = err(type self, x) - -template err*[T](self: var Result[T, cstring], x: string) = - const s = x # Make sure we don't return a dangling pointer - self = err(type self, cstring(s)) - -template err*[T](self: var Result[T, void]) = - ## Set the result as an error - ## Example: `result.err()` - self = err(type self) - -template ok*(v: auto): auto = ok(typeof(result), v) -template ok*(): auto = ok(typeof(result)) - -template err*(v: auto): auto = err(typeof(result), v) -template err*(): auto = err(typeof(result)) - -template isOk*(self: Result): bool = self.oResultPrivate -template isErr*(self: Result): bool = not self.oResultPrivate - -when not defined(nimHasEffectsOfs): - template effectsOf(f: untyped) {.pragma, used.} - -func map*[T0: not void, E; T1: not void]( - self: Result[T0, E], f: proc(x: T0): T1): - Result[T1, E] {.inline, effectsOf: f.} = - ## Transform value using f, or return error - ## - ## ``` - ## let r = Result[int, cstring).ok(42) - ## assert r.map(proc (v: int): int = $v).value() == "42" - ## ``` - case self.oResultPrivate - of true: - when T1 is void: - f(self.vResultPrivate) - result.ok() - else: - result.ok(f(self.vResultPrivate)) - of false: - when E is void: - result.err() - else: - result.err(self.eResultPrivate) - -func map*[T: not void, E]( - self: Result[T, E], f: proc(x: T)): - Result[void, E] {.inline, effectsOf: f.} = - ## Transform value using f, or return error - ## - ## ``` - ## let r = Result[int, cstring).ok(42) - ## assert r.map(proc (v: int): int = $v).value() == "42" - ## ``` - case self.oResultPrivate - of true: - f(self.vResultPrivate) - result.ok() - of false: - when E is void: - result.err() - else: - result.err(self.eResultPrivate) - -func map*[E; T1: not void]( - self: Result[void, E], f: proc(): T1): - Result[T1, E] {.inline, effectsOf: f.} = - ## Transform value using f, or return error - case self.oResultPrivate - of true: - result.ok(f()) - of false: - when E is void: - result.err() - else: - result.err(self.eResultPrivate) - -func map*[E]( - self: Result[void, E], f: proc()): - Result[void, E] {.inline, effectsOf: f.} = - ## Call f if `self` is ok - case self.oResultPrivate - of true: - f() - result.ok() - of false: - when E is void: - result.err() - else: - result.err(self.eResultPrivate) - -func flatMap*[T0: not void, E, T1]( - self: Result[T0, E], f: proc(x: T0): Result[T1, E]): - Result[T1, E] {.inline, effectsOf: f.} = - case self.oResultPrivate - of true: - f(self.vResultPrivate) - of false: - when E is void: - Result[T1, void].err() - else: - Result[T1, E].err(self.eResultPrivate) - -func flatMap*[E, T1]( - self: Result[void, E], f: proc(): Result[T1, E]): - Result[T1, E] {.inline, effectsOf: f.} = - case self.oResultPrivate - of true: - f() - of false: - when E is void: - Result[T1, void].err() - else: - Result[T1, E].err(self.eResultPrivate) - -func mapErr*[T; E0: not void; E1: not void]( - self: Result[T, E0], f: proc(x: E0): E1): - Result[T, E1] {.inline, effectsOf: f.} = - ## Transform error using f, or leave untouched - case self.oResultPrivate - of true: - when T is void: - result.ok() - else: - result.ok(self.vResultPrivate) - of false: - result.err(f(self.eResultPrivate)) - -func mapErr*[T; E1: not void]( - self: Result[T, void], f: proc(): E1): - Result[T, E1] {.inline, effectsOf: f.} = - ## Transform error using f, or return value - case self.oResultPrivate - of true: - when T is void: - result.ok() - else: - result.ok(self.vResultPrivate) - of false: - result.err(f()) - -func mapErr*[T; E0: not void]( - self: Result[T, E0], f: proc(x: E0)): - Result[T, void] {.inline, effectsOf: f.} = - ## Transform error using f, or return value - case self.oResultPrivate - of true: - when T is void: - result.ok() - else: - result.ok(self.vResultPrivate) - of false: - f(self.eResultPrivate) - result.err() - -func mapErr*[T]( - self: Result[T, void], f: proc()): - Result[T, void] {.inline, effectsOf: f.} = - ## Transform error using f, or return value - case self.oResultPrivate - of true: - when T is void: - result.ok() - else: - result.ok(self.vResultPrivate) - of false: - f() - result.err() - -func mapConvert*[T0: not void, E]( - self: Result[T0, E], T1: type): Result[T1, E] {.inline.} = - ## Convert result value to A using an conversion - # Would be nice if it was automatic... - case self.oResultPrivate - of true: - when T1 is void: - result.ok() - else: - result.ok(T1(self.vResultPrivate)) - of false: - when E is void: - result.err() - else: - result.err(self.eResultPrivate) - -func mapCast*[T0: not void, E]( - self: Result[T0, E], T1: type): Result[T1, E] {.inline.} = - ## Convert result value to A using a cast - ## Would be nice with nicer syntax... - case self.oResultPrivate - of true: - when T1 is void: - result.ok() - else: - result.ok(cast[T1](self.vResultPrivate)) - of false: - when E is void: - result.err() - else: - result.err(self.eResultPrivate) - -func mapConvertErr*[T, E0]( - self: Result[T, E0], E1: type): Result[T, E1] {.inline.} = - ## Convert result error to E1 using an conversion - # Would be nice if it was automatic... - when E0 is E1: - result = self - else: - if self.oResultPrivate: - when T is void: - result.ok() - else: - result.ok(self.vResultPrivate) - else: - when E1 is void: - result.err() - else: - result.err(E1(self.eResultPrivate)) - -func mapCastErr*[T, E0]( - self: Result[T, E0], E1: type): Result[T, E1] {.inline.} = - ## Convert result value to A using a cast - ## Would be nice with nicer syntax... - if self.oResultPrivate: - when T is void: - result.ok() - else: - result.ok(self.vResultPrivate) - else: - result.err(cast[E1](self.eResultPrivate)) - -template `and`*[T0, E, T1](self: Result[T0, E], other: Result[T1, E]): Result[T1, E] = - ## Evaluate `other` iff self.isOk, else return error - ## fail-fast - will not evaluate other if a is an error - let s = (self) # TODO avoid copy - case s.oResultPrivate - of true: - other - of false: - when type(self) is type(other): - s - else: - type R = type(other) - when E is void: - err(R) - else: - err(R, s.eResultPrivate) - -template `or`*[T, E0, E1](self: Result[T, E0], other: Result[T, E1]): Result[T, E1] = - ## Evaluate `other` iff `not self.isOk`, else return `self` - ## fail-fast - will not evaluate `other` if `self` is ok - ## - ## ``` - ## func f(): Result[int, SomeEnum] = - ## f2() or err(SomeEnum.V) # Collapse errors from other module / function - ## ``` - let s = (self) # TODO avoid copy - case s.oResultPrivate - of true: - when type(self) is type(other): - s - else: - type R = type(other) - when T is void: - ok(R) - else: - ok(R, s.vResultPrivate) - of false: - other - -template orErr*[T, E0, E1](self: Result[T, E0], error: E1): Result[T, E1] = - ## Evaluate `other` iff `not self.isOk`, else return `self` - ## fail-fast - will not evaluate `error` if `self` is ok - ## - ## ``` - ## func f(): Result[int, SomeEnum] = - ## f2().orErr(SomeEnum.V) # Collapse errors from other module / function - ## ``` - ## - ## ** Experimental, may be removed ** - let s = (self) # TODO avoid copy - type R = Result[T, E1] - case s.oResultPrivate - of true: - when type(self) is R: - s - else: - when T is void: - ok(R) - else: - ok(R, s.vResultPrivate) - of false: - err(R, error) - - -template catch*(body: typed): Result[type(body), ref CatchableError] = - ## Catch exceptions for body and store them in the Result - ## - ## ``` - ## let r = catch: someFuncThatMayRaise() - ## ``` - type R = Result[type(body), ref CatchableError] - - try: - when type(body) is void: - body - R.ok() - else: - R.ok(body) - except CatchableError as eResultPrivate: - R.err(eResultPrivate) - -template capture*[E: Exception](T: type, someExceptionExpr: ref E): Result[T, ref E] = - ## Evaluate someExceptionExpr and put the exception into a result, making sure - ## to capture a call stack at the capture site: - ## - ## ``` - ## let eResultPrivate: Result[void, ValueError] = void.capture((ref ValueError)(msg: "test")) - ## echo eResultPrivate.error().getStackTrace() - ## ``` - type R = Result[T, ref E] - - var ret: R - try: - # TODO is this needed? I think so, in order to grab a call stack, but - # haven't actually tested... - if true: - # I'm sure there's a nicer way - this just works :) - raise someExceptionExpr - except E as caught: - ret = R.err(caught) - ret - -func `==`*[ - T0: not void, E0: not void, - T1: not void, E1: not void]( - lhs: Result[T0, E0], rhs: Result[T1, E1]): bool {.inline.} = - if lhs.oResultPrivate != rhs.oResultPrivate: - false - else: - case lhs.oResultPrivate # and rhs.oResultPrivate implied - of true: - lhs.vResultPrivate == rhs.vResultPrivate - of false: - lhs.eResultPrivate == rhs.eResultPrivate - -func `==`*[E0, E1]( - lhs: Result[void, E0], rhs: Result[void, E1]): bool {.inline.} = - if lhs.oResultPrivate != rhs.oResultPrivate: - false - else: - case lhs.oResultPrivate # and rhs.oResultPrivate implied - of true: - true - of false: - lhs.eResultPrivate == rhs.eResultPrivate - -func `==`*[T0, T1]( - lhs: Result[T0, void], rhs: Result[T1, void]): bool {.inline.} = - if lhs.oResultPrivate != rhs.oResultPrivate: - false - else: - case lhs.oResultPrivate # and rhs.oResultPrivate implied - of true: - lhs.vResultPrivate == rhs.vResultPrivate - of false: - true - -func value*[T, E](self: Result[T, E]): T {.inline.} = - ## Fetch value of result if set, or raise Defect - ## Exception bridge mode: raise given Exception instead - ## See also: Option.get - withAssertOk(self): - when T isnot void: - # TODO: remove result usage. - # A workaround for nim VM bug: - # https://github.com/nim-lang/Nim/issues/22216 - result = self.vResultPrivate - -func value*[T: not void, E](self: var Result[T, E]): var T {.inline.} = - ## Fetch value of result if set, or raise Defect - ## Exception bridge mode: raise given Exception instead - ## See also: Option.get - (block: - withAssertOk(self): addr self.vResultPrivate)[] - -template `[]`*[T: not void, E](self: Result[T, E]): T = - ## Fetch value of result if set, or raise Defect - ## Exception bridge mode: raise given Exception instead - self.value() - -template `[]`*[E](self: Result[void, E]) = - ## Fetch value of result if set, or raise Defect - ## Exception bridge mode: raise given Exception instead - self.value() - -template unsafeValue*[T: not void, E](self: Result[T, E]): T = - ## Fetch value of result if set, undefined behavior if unset - ## See also: `unsafeError` - self.vResultPrivate - -template unsafeValue*[E](self: Result[void, E]) = - ## Fetch value of result if set, undefined behavior if unset - ## See also: `unsafeError` - assert self.oResultPrivate # Emulate field access defect in debug builds - -func tryValue*[T, E](self: Result[T, E]): T {.inline.} = - ## Fetch value of result if set, or raise - ## When E is an Exception, raise that exception - otherwise, raise a ResultError[E] - mixin raiseResultError - case self.oResultPrivate - of false: - self.raiseResultError() - of true: - when T isnot void: - self.vResultPrivate - -func expect*[T, E](self: Result[T, E], m: string): T = - ## Return value of Result, or raise a `Defect` with the given message - use - ## this helper to extract the value when an error is not expected, for example - ## because the program logic dictates that the operation should never fail - ## - ## ```nim - ## let r = Result[int, int].ok(42) - ## # Put here a helpful comment why you think this won't fail - ## echo r.expect("r was just set to ok(42)") - ## ``` - case self.oResultPrivate - of false: - when E isnot void: - raiseResultDefect(m, self.eResultPrivate) - else: - raiseResultDefect(m) - of true: - when T isnot void: - self.vResultPrivate - -func expect*[T: not void, E](self: var Result[T, E], m: string): var T = - (case self.oResultPrivate - of false: - when E isnot void: - raiseResultDefect(m, self.eResultPrivate) - else: - raiseResultDefect(m) - of true: - addr self.vResultPrivate)[] - -func `$`*[T, E](self: Result[T, E]): string = - ## Returns string representation of `self` - case self.oResultPrivate - of true: - when T is void: "ok()" - else: "ok(" & $self.vResultPrivate & ")" - of false: - when E is void: "none()" - else: "err(" & $self.eResultPrivate & ")" - -func error*[T, E](self: Result[T, E]): E = - ## Fetch error of result if set, or raise Defect - case self.oResultPrivate - of true: - when T isnot void: - raiseResultDefect("Trying to access error when value is set", self.vResultPrivate) - else: - raiseResultDefect("Trying to access error when value is set") - of false: - when E isnot void: - self.eResultPrivate - -func tryError*[T, E](self: Result[T, E]): E {.inline.} = - ## Fetch error of result if set, or raise - ## Raises a ResultError[T] - mixin raiseResultOk - case self.oResultPrivate - of true: - self.raiseResultOk() - of false: - when E isnot void: - self.eResultPrivate - -template unsafeError*[T; E: not void](self: Result[T, E]): E = - ## Fetch error of result if set, undefined behavior if unset - ## See also: `unsafeValue` - self.eResultPrivate - -template unsafeError*[T](self: Result[T, void]) = - ## Fetch error of result if set, undefined behavior if unset - ## See also: `unsafeValue` - assert not self.oResultPrivate # Emulate field access defect in debug builds - -func optValue*[T, E](self: Result[T, E]): Opt[T] = - ## Return the value of a Result as an Opt, or none if Result is an error - case self.oResultPrivate - of true: - Opt.some(self.vResultPrivate) - of false: - Opt.none(T) - -func optError*[T, E](self: Result[T, E]): Opt[E] = - ## Return the error of a Result as an Opt, or none if Result is a value - case self.oResultPrivate - of true: - Opt.none(E) - of false: - Opt.some(self.eResultPrivate) - -# Alternative spellings for `value`, for `options` compatibility -template get*[T: not void, E](self: Result[T, E]): T = self.value() -template get*[E](self: Result[void, E]) = self.value() - -template tryGet*[T: not void, E](self: Result[T, E]): T = self.tryValue() -template tryGet*[E](self: Result[void, E]) = self.tryValue() - -template unsafeGet*[T: not void, E](self: Result[T, E]): T = self.unsafeValue() -template unsafeGet*[E](self: Result[void, E]) = self.unsafeValue() - -# `var` overloads should not be needed but result in invalid codegen (!): -# TODO https://github.com/nim-lang/Nim/issues/22049 -func get*[T: not void, E](self: var Result[T, E]): var T = self.value() - -func get*[T, E](self: Result[T, E], otherwise: T): T {.inline.} = - ## Fetch value of result if set, or return the value `otherwise` - ## See `valueOr` for a template version that avoids evaluating `otherwise` - ## unless necessary - case self.oResultPrivate - of true: - self.vResultPrivate - of false: - otherwise - -template isOkOr*[T, E](self: Result[T, E], body: untyped) = - ## Evaluate `body` iff result has been assigned an error - ## `body` is evaluated lazily. - ## - ## Example: - ## ``` - ## let - ## v = Result[int, string].err("hello") - ## x = v.isOkOr: echo "not ok" - ## # experimental: direct error access using an unqualified `error` symbol - ## z = v.isOkOr: echo error - ## ``` - ## - ## `error` access: - ## - ## TODO experimental, might change in the future - ## - ## The template contains a shortcut for accessing the error of the result, - ## it can only be used outside of generic code, - ## see https://github.com/status-im/nim-stew/issues/161#issuecomment-1397121386 - - let s = (self) # TODO avoid copy - case s.oResultPrivate - of false: - when E isnot void: - template error: E {.used, inject.} = s.eResultPrivate - body - of true: - discard - -template isErrOr*[T, E](self: Result[T, E], body: untyped) = - ## Evaluate `body` iff result has been assigned a value - ## `body` is evaluated lazily. - ## - ## Example: - ## ``` - ## let - ## v = Result[int, string].err("hello") - ## x = v.isOkOr: echo "not ok" - ## # experimental: direct error access using an unqualified `error` symbol - ## z = v.isOkOr: echo error - ## ``` - ## - ## `value` access: - ## - ## TODO experimental, might change in the future - ## - ## The template contains a shortcut for accessing the value of the result, - ## it can only be used outside of generic code, - ## see https://github.com/status-im/nim-stew/issues/161#issuecomment-1397121386 - - let s = (self) # TODO avoid copy - case s.oResultPrivate - of true: - when T isnot void: - template value: T {.used, inject.} = s.vResultPrivate - body - of false: - discard - -template valueOr*[T: not void, E](self: Result[T, E], def: untyped): T = - ## Fetch value of result if set, or evaluate `def` - ## `def` is evaluated lazily, and must be an expression of `T` or exit - ## the scope (for example using `return` / `raise`) - ## - ## See `isOkOr` for a version that works with `Result[void, E]`. - ## - ## Example: - ## ``` - ## let - ## v = Result[int, string].err("hello") - ## x = v.valueOr: 42 # x == 42 now - ## y = v.valueOr: raise (ref ValueError)(msg: "v is an error, gasp!") - ## # experimental: direct error access using an unqualified `error` symbol - ## z = v.valueOr: raise (ref ValueError)(msg: error) - ## ``` - ## - ## `error` access: - ## - ## TODO experimental, might change in the future - ## - ## The template contains a shortcut for accessing the error of the result, - ## it can only be used outside of generic code, - ## see https://github.com/status-im/nim-stew/issues/161#issuecomment-1397121386 - ## - let s = (self) # TODO avoid copy - case s.oResultPrivate - of true: - s.vResultPrivate - of false: - when E isnot void: - template error: E {.used, inject.} = s.eResultPrivate - def - -template errorOr*[T; E: not void](self: Result[T, E], def: untyped): E = - ## Fetch error of result if not set, or evaluate `def` - ## `def` is evaluated lazily, and must be an expression of `T` or exit - ## the scope (for example using `return` / `raise`) - ## - ## See `isErrOr` for a version that works with `Result[T, void]`. - let s = (self) # TODO avoid copy - case s.oResultPrivate - of false: - s.eResultPrivate - of true: - when T isnot void: - template value: T {.used, inject.} = s.vResultPrivate - def - -func flatten*[T, E](self: Result[Result[T, E], E]): Result[T, E] = - ## Remove one level of nesting - case self.oResultPrivate - of true: - self.vResultPrivate - of false: - when E is void: - err(Result[T, E]) - else: - err(Result[T, E], self.error) - -func filter*[T, E]( - self: Result[T, E], - callback: proc(x: T): Result[void, E]): - Result[T, E] {.effectsOf: callback.} = - ## Apply `callback` to the `self`, iff `self` is not an error. If `callback` - ## returns an error, return that error, else return `self` - - case self.oResultPrivate - of true: - callback(self.vResultPrivate) and self - of false: - self - -func filter*[E]( - self: Result[void, E], - callback: proc(): Result[void, E]): - Result[void, E] {.effectsOf: callback.} = - ## Apply `callback` to the `self`, iff `self` is not an error. If `callback` - ## returns an error, return that error, else return `self` - - case self.oResultPrivate - of true: - callback() and self - of false: - self - -func filter*[T]( - self: Result[T, void], - callback: proc(x: T): bool): - Result[T, void] {.effectsOf: callback.} = - ## Apply `callback` to the `self`, iff `self` is not an error. If `callback` - ## returns an error, return that error, else return `self` - - case self.oResultPrivate - of true: - if callback(self.vResultPrivate): - self - else: - Result[T, void].err() - of false: - self - -# Options compatibility - -template some*[T](O: type Opt, v: T): Opt[T] = - ## Create an `Opt` set to a value - ## - ## ``` - ## let oResultPrivate = Opt.some(42) - ## assert oResultPrivate.isSome and oResultPrivate.value() == 42 - ## ``` - Opt[T].ok(v) - -template none*(O: type Opt, T: type): Opt[T] = - ## Create an `Opt` set to none - ## - ## ``` - ## let oResultPrivate = Opt.none(int) - ## assert oResultPrivate.isNone - ## ``` - Opt[T].err() - -template isSome*(oResultPrivate: Opt): bool = - ## Alias for `isOk` - isOk oResultPrivate - -template isNone*(oResultPrivate: Opt): bool = - ## Alias of `isErr` - isErr oResultPrivate - -# Syntactic convenience - -template `?`*[T, E](self: Result[T, E]): auto = - ## Early return - if self is an error, we will return from the current - ## function, else we'll move on.. - ## - ## ``` - ## let v = ? funcWithResult() - ## echo v # prints value, not Result! - ## ``` - ## Experimental - # TODO the v copy is here to prevent multiple evaluations of self - could - # probably avoid it with some fancy macro magic.. - let v = (self) - case v.oResultPrivate - of false: - when typeof(result) is typeof(v): - return v - else: - when E is void: - return err(typeof(result)) - else: - return err(typeof(result), v.eResultPrivate) - of true: - when not(T is void): - v.vResultPrivate - -# Collection integration - -iterator values*[T, E](self: Result[T, E]): T = - ## Iterate over a Result as a 0/1-item collection, returning its value if set - case self.oResultPrivate - of true: - yield self.vResultPrivate - of false: - discard - -iterator errors*[T, E](self: Result[T, E]): E = - ## Iterate over a Result as a 0/1-item collection, returning its error if set - case self.oResultPrivate - of false: - yield self.eResultPrivate - of true: - discard - -iterator items*[T](self: Opt[T]): T = - ## Iterate over an Opt as a 0/1-item collection, returning its value if set - case self.oResultPrivate - of true: - yield self.vResultPrivate - of false: - discard - -iterator mvalues*[T, E](self: var Result[T, E]): var T = - case self.oResultPrivate - of true: - yield self.vResultPrivate - of false: - discard - -iterator merrors*[T, E](self: var Result[T, E]): var E = - case self.oResultPrivate - of false: - yield self.eResultPrivate - of true: - discard - -iterator mitems*[T](self: var Opt[T]): var T = - case self.oResultPrivate - of true: - yield self.vResultPrivate - of false: - discard - -func containsValue*(self: Result, v: auto): bool = - ## Return true iff the given result is set to a value that equals `v` - case self.oResultPrivate - of true: - self.vResultPrivate == v - of false: - false - -func containsError*(self: Result, e: auto): bool = - ## Return true iff the given result is set to an error that equals `e` - case self.oResultPrivate - of false: - self.eResultPrivate == e - of true: - false - -func contains*(self: Opt, v: auto): bool = - ## Return true iff the given `Opt` is set to a value that equals `v` - can - ## also be used in the "infix" `in` form: - ## - ## ```nim - ## assert "value" in Opt.some("value") - ## ``` - case self.oResultPrivate - of true: - self.vResultPrivate == v - of false: - false +import pkg/results +export results diff --git a/tests/all_tests.nim b/tests/all_tests.nim index 5946645..26a923e 100644 --- a/tests/all_tests.nim +++ b/tests/all_tests.nim @@ -32,6 +32,4 @@ import test_sequtils2, test_sets, test_templateutils, - test_results, - test_results2, test_winacl diff --git a/tests/test_results.nim b/tests/test_results.nim deleted file mode 100644 index aa520b5..0000000 --- a/tests/test_results.nim +++ /dev/null @@ -1,532 +0,0 @@ -{.used.} - -# nim-result is also available stand-alone from https://github.com/arnetheduck/nim-result/ - -import ../stew/results - -type R = Result[int, string] - -# Basic usage, producer - -block: - func works(): R = R.ok(42) - func works2(): R = result.ok(42) - func works3(): R = ok(42) - - func fails(): R = R.err("dummy") - func fails2(): R = result.err("dummy") - func fails3(): R = err("dummy") - - let - rOk = works() - rOk2 = works2() - rOk3 = works3() - - rErr = fails() - rErr2 = fails2() - rErr3 = fails3() - - doAssert rOk.isOk - doAssert rOk2.isOk - doAssert rOk3.isOk - doAssert (not rOk.isErr) - - doAssert rErr.isErr - doAssert rErr2.isErr - doAssert rErr3.isErr - - # Mutate - var x = rOk - x.err("failed now") - doAssert x.isErr - doAssert x.error == "failed now" - - # Combine - doAssert (rOk and rErr).isErr - doAssert (rErr and rOk).isErr - doAssert (rOk or rErr).isOk - doAssert (rErr or rOk).isOk - - # Fail fast - proc failFast(): int = raiseAssert "shouldn't evaluate" - proc failFastR(): R = raiseAssert "shouldn't evaluate" - - doAssert (rErr and failFastR()).isErr - doAssert (rOk or failFastR()).isOk - - # `and` heterogenous types - doAssert (rOk and Result[string, string].ok($rOk.get())).get() == $(rOk[]) - - # `or` heterogenous types - doAssert (rErr or Result[int, int].err(len(rErr.error))).error == len(rErr.error) - - # Exception on access - doAssert (try: (discard rOk.tryError(); false) except ResultError[int]: true) - doAssert (try: (discard rErr.tryGet(); false) except ResultError[string]: true) - - # Value access or default - doAssert rOk.get(100) == rOk.get() - doAssert rErr.get(100) == 100 - - doAssert rOk.get() == rOk.unsafeGet() - - rOk.isOkOr: raiseAssert "should not end up in here" - rErr.isErrOr: raiseAssert "should not end up in here" - - rErr.isOkOr: - doAssert error == rErr.error() - - rOk.isErrOr: - doAssert value == rOk.value() - - doAssert rOk.valueOr(failFast()) == rOk.value() - let rErrV = rErr.valueOr: - error.len - doAssert rErrV == rErr.error.len() - - let rOkV = rOk.errorOr: - $value - doAssert rOkV == $rOk.get() - - # Exceptions -> results - block: - func raises(): int = - raise (ref CatchableError)(msg: "hello") - func raisesVoid() = - raise (ref CatchableError)(msg: "hello") - - let c = catch: - raises() - doAssert c.isErr - - when (NimMajor, NimMinor) >= (1, 6): - # Earlier versions complain about the type of the raisesVoid expression - let d = catch: - raisesVoid() - doAssert d.isErr - - # De-reference - when (NimMajor, NimMinor) >= (1, 6): - {.warning[BareExcept]:off.} - - try: - echo rErr[] - doAssert false - except: - discard - - when (NimMajor, NimMinor) >= (1, 6): - {.warning[BareExcept]:on.} - - # Comparisons - doAssert (rOk == rOk) - doAssert (rErr == rErr) - doAssert (rOk != rErr) - - # Mapping - doAssert (rOk.map(func(x: int): string = $x)[] == $rOk.value) - doAssert (rOk.map(func(x: int) = discard)).isOk() - - doAssert (rOk.flatMap( - proc(x: int): Result[string, string] = Result[string, string].ok($x))[] == $rOk.value) - - doAssert (rErr.mapErr(func(x: string): string = x & "no!").error == (rErr.error & "no!")) - - # Casts and conversions - doAssert rOk.mapConvert(int64)[] == int64(42) - doAssert rOk.mapConvert(uint64)[] == uint64(42) - doAssert rOk.mapCast(int8)[] == int8(42) - - doAssert (rErr.orErr(32)).error == 32 - doAssert (rOk.orErr(failFast())).get() == rOk.get() - - doAssert rErr.mapConvertErr(cstring).error() == cstring(rErr.error()) - doAssert rErr.mapCastErr(seq[byte]).error() == cast[seq[byte]](rErr.error()) - - # string conversion - doAssert $rOk == "ok(42)" - doAssert $rErr == "err(dummy)" - - # Exception interop - let e = capture(int, (ref ValueError)(msg: "test")) - doAssert e.isErr - doAssert e.error.msg == "test" - - try: - discard rOk.tryError() - doAssert false, "should have raised" - except ValueError: - discard - - try: - discard e.tryGet() - doAssert false, "should have raised" - except ValueError as e: - doAssert e.msg == "test" - - # Nice way to checks - if (let v = works(); v.isOk): - doAssert v[] == v.value - - # Expectations - doAssert rOk.expect("testOk never fails") == 42 - - # Conversions to Opt - doAssert rOk.optValue() == Opt.some(rOk.get()) - doAssert rOk.optError().isNone() - doAssert rErr.optValue().isNone() - doAssert rErr.optError() == Opt.some(rErr.error()) - - # Question mark operator - func testQn(): Result[int, string] = - let x = ?works() - ?works() - ok(x) - - 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() - ok(x == 0) - - doAssert testQn()[] == 0 - doAssert testQn2().isErr - doAssert testQn3()[] - - proc heterOr(): Result[int, int] = - let value = ? (rErr or err(42)) # TODO ? binds more tightly than `or` - can that be fixed? - doAssert value + 1 == value, "won't reach, ? will shortcut execution" - ok(value) - - doAssert heterOr().error() == 42 - - # Flatten - doAssert Result[R, string].ok(rOk).flatten() == rOk - doAssert Result[R, string].ok(rErr).flatten() == rErr - - # Filter - doAssert rOk.filter(proc(x: int): auto = Result[void, string].ok()) == rOk - doAssert rOk.filter(proc(x: int): auto = Result[void, string].err("filter")).error == "filter" - doAssert rErr.filter(proc(x: int): auto = Result[void, string].err("filter")) == rErr - - # Collections - block: - var i = 0 - for v in rOk.values: - doAssert v == rOk.value() - i += 1 - doAssert i == 1 - - for v in rOk.errors: - raiseAssert "not an error" - - doAssert rOk.containsValue(rOk.value()) - doAssert not rOk.containsValue(rOk.value() + 1) - - doAssert not rOk.containsError("test") - - block: - var i = 0 - for v in rErr.values: - raiseAssert "not a value" - - for v in rErr.errors: - doAssert v == rErr.error() - i += 1 - doAssert i == 1 - - doAssert rErr.containsError(rErr.error()) - doAssert not rErr.containsError(rErr.error() & "X") - - doAssert not rErr.containsValue(42) - -# Exception conversions - toException must not be inside a block -type - AnEnum = enum - anEnumA - anEnumB - AnException = ref object of CatchableError - v: AnEnum - -func toException(v: AnEnum): AnException = AnException(v: v) - -func testToException(): int = - try: - var r = Result[int, AnEnum].err(anEnumA) - r.tryGet - except AnException: - 42 - -doAssert testToException() == 42 - -type - AnEnum2 = enum - anEnum2A - anEnum2B - -func testToString(): int = - try: - var r = Result[int, AnEnum2].err(anEnum2A) - r.tryGet - except ResultError[AnEnum2]: - 42 - -doAssert testToString() == 42 - -block: # Result[void, E] - type VoidRes = Result[void, int] - - func worksVoid(): VoidRes = VoidRes.ok() - func worksVoid2(): VoidRes = result.ok() - func worksVoid3(): VoidRes = ok() - - func failsVoid(): VoidRes = VoidRes.err(42) - func failsVoid2(): VoidRes = result.err(42) - func failsVoid3(): VoidRes = err(42) - - let - vOk = worksVoid() - vOk2 = worksVoid2() - vOk3 = worksVoid3() - - vErr = failsVoid() - vErr2 = failsVoid2() - vErr3 = failsVoid3() - - doAssert vOk.isOk - doAssert vOk2.isOk - doAssert vOk3.isOk - doAssert (not vOk.isErr) - - doAssert vErr.isErr - doAssert vErr2.isErr - doAssert vErr3.isErr - - vOk.get() - vOk.unsafeGet() - vOk.expect("should never fail") - vOk[] - - # Comparisons - doAssert (vOk == vOk) - doAssert (vErr == vErr) - doAssert (vOk != vErr) - - # Mapping - doAssert vOk.map(proc (): int = 42).get() == 42 - vOk.map(proc () = discard).get() - - vOk.mapErr(proc(x: int): int = 10).get() - vOk.mapErr(proc(x: int) = discard).get() - - doAssert vErr.mapErr(proc(x: int): int = 10).error() == 10 - - # string conversion - doAssert $vOk == "ok()" - doAssert $vErr == "err(42)" - - # Question mark operator - func voidF(): VoidRes = - ok() - - func voidF2(): Result[int, int] = - ? voidF() - - ok(42) - - doAssert voidF2().isOk - - # flatten - doAssert Result[VoidRes, int].ok(vOk).flatten() == vOk - doAssert Result[VoidRes, int].ok(vErr).flatten() == vErr - - # Filter - doAssert vOk.filter(proc(): auto = Result[void, int].ok()) == vOk - doAssert vOk.filter(proc(): auto = Result[void, int].err(100)).error == 100 - doAssert vErr.filter(proc(): auto = Result[void, int].err(100)) == vErr - -block: # Result[T, void] aka `Opt` - type OptInt = Result[int, void] - - func worksOpt(): OptInt = OptInt.ok(42) - func worksOpt2(): OptInt = result.ok(42) - func worksOpt3(): OptInt = ok(42) - - func failsOpt(): OptInt = OptInt.err() - func failsOpt2(): OptInt = result.err() - func failsOpt3(): OptInt = err() - - let - oOk = worksOpt() - oOk2 = worksOpt2() - oOk3 = worksOpt3() - - oErr = failsOpt() - oErr2 = failsOpt2() - oErr3 = failsOpt3() - - doAssert oOk.isOk - doAssert oOk2.isOk - doAssert oOk3.isOk - doAssert (not oOk.isErr) - - doAssert oErr.isErr - doAssert oErr2.isErr - doAssert oErr3.isErr - - # Comparisons - doAssert (oOk == oOk) - doAssert (oErr == oErr) - doAssert (oOk != oErr) - - doAssert oOk.get() == oOk.unsafeGet() - oErr.error() - oErr.unsafeError() - - # Mapping - doAssert oOk.map(proc(x: int): string = $x).get() == $oOk.get() - oOk.map(proc(x: int) = discard).get() - - doAssert oOk.mapErr(proc(): int = 10).get() == oOk.get() - doAssert oOk.mapErr(proc() = discard).get() == oOk.get() - - doAssert oErr.mapErr(proc(): int = 10).error() == 10 - - # string conversion - doAssert $oOk == "ok(42)" - doAssert $oErr == "none()" - - proc optQuestion(): OptInt = - let v = ? oOk - ok(v) - - doAssert optQuestion().isOk() - - # Flatten - doAssert Result[OptInt, void].ok(oOk).flatten() == oOk - doAssert Result[OptInt, void].ok(oErr).flatten() == oErr - - # Filter - doAssert oOk.filter(proc(x: int): auto = Result[void, void].ok()) == oOk - doAssert oOk.filter(proc(x: int): auto = Result[void, void].err()).isErr() - doAssert oErr.filter(proc(x: int): auto = Result[void, void].err()) == oErr - - doAssert oOk.filter(proc(x: int): bool = true) == oOk - doAssert oOk.filter(proc(x: int): bool = false).isErr() - doAssert oErr.filter(proc(x: int): bool = true) == oErr - - doAssert Opt.some(42).get() == 42 - doAssert Opt.none(int).isNone() - - # Construct Result from Opt - doAssert oOk.orErr("error").value() == oOk.get() - doAssert oErr.orErr("error").error() == "error" - - # Collections - block: - var i = 0 - for v in oOk: - doAssert v == oOk.value() - i += 1 - doAssert i == 1 - - doAssert oOk.value() in oOk - doAssert oOk.value() + 1 notin oOk - -block: # `cstring` dangling reference protection - type CSRes = Result[void, cstring] - - func cstringF(s: string): CSRes = - when compiles(err(s)): - doAssert false - - discard cstringF("test") - -block: # Experiments - # Can formalise it into a template (https://github.com/arnetheduck/nim-result/issues/8) - template `?=`(v: untyped{nkIdent}, vv: Result): bool = - (let vr = vv; template v: auto {.used.} = unsafeGet(vr); vr.isOk) - - if f ?= Result[int, string].ok(42): - doAssert f == 42 - - # TODO there's a bunch of operators that one could lift through magic - this - # is mainly an example - template `+`(self, other: Result): untyped = - ## Perform `+` on the values of self and other, if both are ok - type R = type(other) - if self.isOk: - if other.isOk: - R.ok(self.value + other.value) - else: - R.err(other.error) - else: - R.err(self.error) - - let rOk = Result[int, string].ok(42) - # Simple lifting.. - doAssert (rOk + rOk)[] == rOk.value + rOk.value - - iterator items[T, E](self: Result[T, E]): T = - ## Iterate over result as if it were a collection of either 0 or 1 items - ## TODO should a Result[seq[X]] iterate over items in seq? there are - ## arguments for and against - if self.isOk: - yield self.value - - # Iteration - var counter2 = 0 - for v in rOk: - counter2 += 1 - - doAssert counter2 == 1, "one-item collection when set" - -block: # Constants - # TODO https://github.com/nim-lang/Nim/issues/20699 - type - WithOpt = object - opt: Opt[int] - const - noneWithOpt = - WithOpt(opt: Opt.none(int)) - proc checkIt(v: WithOpt) = - doAssert v.opt.isNone() - checkIt(noneWithOpt) - - block: # TODO https://github.com/nim-lang/Nim/issues/22049 - var v: Result[(seq[int], seq[int]), int] - v.ok((@[1], @[2])) - let (a, b) = v.get() - doAssert a == [1] and b == [2] - let (c, d) = v.tryGet() - doAssert c == [1] and d == [2] - let (e, f) = v.unsafeGet() - doAssert e == [1] and f == [2] - -block: - # withAssertOk evaluated as statement instead of expr - # https://github.com/nim-lang/Nim/issues/22216 - func bug(): Result[uint16, string] = - ok(1234) - - const - x = bug() - y = x.value() - - doAssert y == 1234 - - when (NimMajor, NimMinor) >= (1,6): - # pre 1.6 nim vm have worse bug - static: - var z = bug() - z.value() = 15 - let w = z.get() - doAssert w == 15 - - let - xx = bug() - yy = x.value() - - doAssert yy == 1234 diff --git a/tests/test_results2.nim b/tests/test_results2.nim deleted file mode 100644 index 6efd27a..0000000 --- a/tests/test_results2.nim +++ /dev/null @@ -1,13 +0,0 @@ -import ../stew/results - -{.used.} - -# Oddly, this piece of code works when placed in `test_results.nim` -# See also https://github.com/status-im/nim-stew/pull/167 - -template repeater(b: Opt[int]): untyped = - # Check that Result can be used inside a template - this fails - # sometimes with field access errors as noted in above issue - b -let x = repeater(Opt.none(int)) -doAssert x.isNone()