Skip to content

Commit e76b506

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 266e900 commit e76b506

File tree

2 files changed

+76
-9
lines changed

2 files changed

+76
-9
lines changed

stew/results.nim

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1051,18 +1051,44 @@ template `?`*[T, E](self: Result[T, E]): auto =
10511051
## let v = ? funcWithResult()
10521052
## echo v # prints value, not Result!
10531053
## ```
1054+
##
1055+
## ``assignResult?`` extension point
1056+
##
1057+
## If a template / function named ``assignResult?`` exists in the scope, it will
1058+
## be called instead of assigning the result - this can be used to intercept
1059+
## the assignment to `result` that implicitly happens on early return in case
1060+
## of error.
1061+
##
1062+
## To prevent conflicts, it is recommended that ``assignResult?`` is declared
1063+
## close to the scope of `?` (as opposed to a globally visible symbol)
1064+
##
1065+
## ```nim
1066+
## proc f(): Result[...] =
1067+
## template assignResult(v: Result) = ...
1068+
## let a = ? f2()
1069+
## ```
1070+
##
10541071
## Experimental
1055-
# TODO the v copy is here to prevent multiple evaluations of self - could
1056-
# probably avoid it with some fancy macro magic..
1057-
let v = (self)
1072+
mixin `assignResult?`
1073+
1074+
let v = (self) # TODO avoid copy
10581075
if not v.oResultPrivate:
1059-
when typeof(result) is typeof(v):
1060-
return v
1061-
else:
1062-
when E is void:
1063-
return err(typeof(result))
1076+
when declared(`assignResult?`):
1077+
when typeof(result) is typeof(v):
1078+
`assignResult?`(v)
1079+
elif E is void:
1080+
`assignResult?`(err(typeof(result)))
10641081
else:
1065-
return err(typeof(result), v.eResultPrivate)
1082+
`assignResult?`(err(typeof(result), v.eResultPrivate))
1083+
return
1084+
else:
1085+
return
1086+
when typeof(result) is typeof(v):
1087+
v
1088+
elif E is void:
1089+
err(typeof(result))
1090+
else:
1091+
err(typeof(result), v.eResultPrivate)
10661092

10671093
when not(T is void):
10681094
v.vResultPrivate

tests/test_results.nim

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,3 +440,44 @@ block: # Constants
440440
proc checkIt(v: WithOpt) =
441441
doAssert v.opt.isNone()
442442
checkIt(noneWithOpt)
443+
444+
proc testAssignResult() =
445+
var assigned: bool
446+
template `assignResult?`(v: Result[int, string]) =
447+
assigned = true
448+
result = v
449+
450+
proc failed(): Result[int, string] =
451+
err("fail")
452+
453+
proc calling(): Result[int, string] =
454+
let _ = ? failed()
455+
doAssert false
456+
457+
let r = calling()
458+
doAssert assigned
459+
doAssert r == Result[int, string].err("fail")
460+
461+
462+
proc testAssignResultGeneric[T]() =
463+
var assigned: bool
464+
template `assignResult?`(v: Result[T, string]) =
465+
mixin result
466+
assigned = true
467+
result = v
468+
469+
proc failed(): Result[int, string] =
470+
err("fail")
471+
472+
proc calling(): Result[int, string] =
473+
let _ = ? failed()
474+
doAssert false
475+
476+
let r = calling()
477+
doAssert assigned
478+
doAssert r == Result[int, string].err("fail")
479+
480+
481+
testAssignResult()
482+
483+
testAssignResultGeneric[int]()

0 commit comments

Comments
 (0)