Skip to content

Commit

Permalink
result: add result assignment extenstion point for ?
Browse files Browse the repository at this point in the history
When `?` exits early, it does so using variations `return v` - however,
in frameworks such as `chronos`, we need to change `return v` to
`future.complete(v)` - this feature allows chronos to inject a template
that performs this assignment instead of using the "ordinary" flow.

Risk:

`assignResult` might already be taken as a name, and multiple frameworks
might compete for the functionality.

Potential alternative:

Instead of `assignResult`, this construct could be called
`returnResult`, and it would be responsible for breaking the control
flow (performing the `return` in addition to assigning the result).

Other interactions:

#34 proposes a general-purpose
conversion framework - this could be used to automatically convert the
error based on existing conversions - the use case is that oftentimes,
one error needs to be converted to another in a call chain and injecting
a converter simplifies this flow - this might however create an
problematic interaction with an `assignResult` template in the future.
  • Loading branch information
arnetheduck committed Jan 19, 2023
1 parent 4e25610 commit 4577da4
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 6 deletions.
39 changes: 33 additions & 6 deletions stew/results.nim
Original file line number Diff line number Diff line change
Expand Up @@ -953,25 +953,52 @@ template isNone*(o: Opt): bool =
# Syntactic convenience

template `?`*[T, E](self: Result[T, E]): auto =
mixin assignResult
## 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!
## ```
##
## `assignResult` extension point
##
## If a template / function named `assignResult` exists in the scope, it will
## be called instead of assigning the result - this can be used to intercept
## the assignment to `result` that implicitly happens on early return in case
## of error.
##
## To prevent conflicts, it is recommended that `assignResult` is declared
## close to the scope of `?` (as opposed to a globally visible symbol)
##
## ```nim
## proc f(): Result[...] =
## template assignResult(v: Result) = ...
## let a = ? f2()
## ```
##
## 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)
if not v.o:
when typeof(result) is typeof(v):
return v
else:
when E is void:
return err(typeof(result))
when declared(assignResult):
when typeof(result) is typeof(v):
assignResult(v)
elif E is void:
assignResult(err(typeof(result)))
else:
return err(typeof(result), v.e)
assignResult(err(typeof(result), v.e))
return
else:
return
when typeof(result) is typeof(v):
v
elif E is void:
err(typeof(result))
else:
err(typeof(result), v.e)

when not(T is void):
v.v
19 changes: 19 additions & 0 deletions tests/test_results.nim
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,25 @@ block: # Experiments

doAssert counter2 == 1, "one-item collection when set"

proc testAssignResult() =
var assigned: bool
template assignResult(v: Result[int, string]) =
assigned = true
result = v

proc failed(): Result[int, string] =
err("fail")

proc calling(): Result[int, string] =
let _ = ? failed()
doAssert false

let r = calling()
doAssert assigned
doAssert r == Result[int, string].err("fail")

testAssignResult()

block: # Constants
# TODO https://github.com/nim-lang/Nim/issues/20699
type
Expand Down

0 comments on commit 4577da4

Please sign in to comment.