Skip to content

Commit 59c7673

Browse files
committed
result: add result assignment extenstion point for ?
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.
1 parent 6c97f11 commit 59c7673

File tree

2 files changed

+95
-6
lines changed

2 files changed

+95
-6
lines changed

stew/results.nim

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1065,18 +1065,44 @@ template `?`*[T, E](self: Result[T, E]): auto =
10651065
## let v = ? funcWithResult()
10661066
## echo v # prints value, not Result!
10671067
## ```
1068+
##
1069+
## ``assignResult?`` extension point
1070+
##
1071+
## If a template / function named ``assignResult?`` exists in the scope, it will
1072+
## be called instead of assigning the result - this can be used to intercept
1073+
## the assignment to `result` that implicitly happens on early return in case
1074+
## of error.
1075+
##
1076+
## To prevent conflicts, it is recommended that ``assignResult?`` is declared
1077+
## close to the scope of `?` (as opposed to a globally visible symbol)
1078+
##
1079+
## ```nim
1080+
## proc f(): Result[...] =
1081+
## template `assignResult?`(v: Result) = ...
1082+
## let a = ? f2()
1083+
## ```
1084+
##
10681085
## Experimental
10691086
# TODO the v copy is here to prevent multiple evaluations of self - could
10701087
# probably avoid it with some fancy macro magic..
10711088
let v = (self)
10721089
if not v.oResultPrivate:
1073-
when typeof(result) is typeof(v):
1074-
return v
1075-
else:
1076-
when E is void:
1077-
return err(typeof(result))
1090+
when compiles(`assignResult?`(default(typeof(result)))):
1091+
when typeof(result) is typeof(v):
1092+
`assignResult?`(v)
1093+
elif E is void:
1094+
`assignResult?`(err(typeof(result)))
10781095
else:
1079-
return err(typeof(result), v.eResultPrivate)
1096+
`assignResult?`(err(typeof(result), v.eResultPrivate))
1097+
return
1098+
else:
1099+
return
1100+
when typeof(result) is typeof(v):
1101+
v
1102+
elif E is void:
1103+
err(typeof(result))
1104+
else:
1105+
err(typeof(result), v.eResultPrivate)
10801106

10811107
when not(T is void):
10821108
v.vResultPrivate

tests/test_results.nim

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,3 +492,66 @@ block: # Constants
492492
doAssert c == [1] and d == [2]
493493
let (e, f) = v.unsafeGet()
494494
doAssert e == [1] and f == [2]
495+
496+
proc testAssignResult() =
497+
var assigned: bool
498+
template `assignResult?`(v: Result) =
499+
assigned = true
500+
result = v
501+
502+
proc failed(): Result[int, string] =
503+
err("fail")
504+
505+
proc calling(): Result[int, string] =
506+
let _ = ? failed()
507+
doAssert false
508+
509+
let r = calling()
510+
doAssert assigned
511+
doAssert r == Result[int, string].err("fail")
512+
513+
assigned = false
514+
515+
proc emptyErr: Result[int, void] =
516+
return err()
517+
518+
proc emptyErr2: Result[int, void] =
519+
let _ = ? emptyErr()
520+
doAssert false
521+
522+
doAssert emptyErr2().isErr()
523+
doAssert assigned
524+
525+
proc testAssignResultGeneric[T]() =
526+
var assigned: bool
527+
template `assignResult?`(v: Result) =
528+
mixin result
529+
assigned = true
530+
result = v
531+
532+
proc failed(): Result[T, string] =
533+
err("fail")
534+
535+
proc calling(): Result[T, string] =
536+
let _ = ? failed()
537+
doAssert false
538+
539+
let r = calling()
540+
doAssert assigned
541+
doAssert r == Result[T, string].err("fail")
542+
543+
assigned = false
544+
545+
proc emptyErr: Result[T, void] =
546+
return err()
547+
548+
proc emptyErr2: Result[T, void] =
549+
let _ = ? emptyErr()
550+
doAssert false
551+
552+
doAssert emptyErr2().isErr()
553+
doAssert assigned
554+
555+
testAssignResult()
556+
557+
testAssignResultGeneric[int]()

0 commit comments

Comments
 (0)