Skip to content

Commit e68ca83

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 13e55ed commit e68ca83

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
@@ -1061,18 +1061,44 @@ template `?`*[T, E](self: Result[T, E]): auto =
10611061
## let v = ? funcWithResult()
10621062
## echo v # prints value, not Result!
10631063
## ```
1064+
##
1065+
## ``assignResult?`` extension point
1066+
##
1067+
## If a template / function named ``assignResult?`` exists in the scope, it will
1068+
## be called instead of assigning the result - this can be used to intercept
1069+
## the assignment to `result` that implicitly happens on early return in case
1070+
## of error.
1071+
##
1072+
## To prevent conflicts, it is recommended that ``assignResult?`` is declared
1073+
## close to the scope of `?` (as opposed to a globally visible symbol)
1074+
##
1075+
## ```nim
1076+
## proc f(): Result[...] =
1077+
## template `assignResult?`(v: Result) = ...
1078+
## let a = ? f2()
1079+
## ```
1080+
##
10641081
## Experimental
10651082
# TODO the v copy is here to prevent multiple evaluations of self - could
10661083
# probably avoid it with some fancy macro magic..
10671084
let v = (self)
10681085
if not v.oResultPrivate:
1069-
when typeof(result) is typeof(v):
1070-
return v
1071-
else:
1072-
when E is void:
1073-
return err(typeof(result))
1086+
when compiles(`assignResult?`(default(typeof(result)))):
1087+
when typeof(result) is typeof(v):
1088+
`assignResult?`(v)
1089+
elif E is void:
1090+
`assignResult?`(err(typeof(result)))
10741091
else:
1075-
return err(typeof(result), v.eResultPrivate)
1092+
`assignResult?`(err(typeof(result), v.eResultPrivate))
1093+
return
1094+
else:
1095+
return
1096+
when typeof(result) is typeof(v):
1097+
v
1098+
elif E is void:
1099+
err(typeof(result))
1100+
else:
1101+
err(typeof(result), v.eResultPrivate)
10761102

10771103
when not(T is void):
10781104
v.vResultPrivate

tests/test_results.nim

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,3 +482,66 @@ block: # Constants
482482
proc checkIt(v: WithOpt) =
483483
doAssert v.opt.isNone()
484484
checkIt(noneWithOpt)
485+
486+
proc testAssignResult() =
487+
var assigned: bool
488+
template `assignResult?`(v: Result) =
489+
assigned = true
490+
result = v
491+
492+
proc failed(): Result[int, string] =
493+
err("fail")
494+
495+
proc calling(): Result[int, string] =
496+
let _ = ? failed()
497+
doAssert false
498+
499+
let r = calling()
500+
doAssert assigned
501+
doAssert r == Result[int, string].err("fail")
502+
503+
assigned = false
504+
505+
proc emptyErr: Result[int, void] =
506+
return err()
507+
508+
proc emptyErr2: Result[int, void] =
509+
let _ = ? emptyErr()
510+
doAssert false
511+
512+
doAssert emptyErr2().isErr()
513+
doAssert assigned
514+
515+
proc testAssignResultGeneric[T]() =
516+
var assigned: bool
517+
template `assignResult?`(v: Result) =
518+
mixin result
519+
assigned = true
520+
result = v
521+
522+
proc failed(): Result[T, string] =
523+
err("fail")
524+
525+
proc calling(): Result[T, string] =
526+
let _ = ? failed()
527+
doAssert false
528+
529+
let r = calling()
530+
doAssert assigned
531+
doAssert r == Result[T, string].err("fail")
532+
533+
assigned = false
534+
535+
proc emptyErr: Result[T, void] =
536+
return err()
537+
538+
proc emptyErr2: Result[T, void] =
539+
let _ = ? emptyErr()
540+
doAssert false
541+
542+
doAssert emptyErr2().isErr()
543+
doAssert assigned
544+
545+
testAssignResult()
546+
547+
testAssignResultGeneric[int]()

0 commit comments

Comments
 (0)