nim-chronos
nim-chronos copied to clipboard
RFC: inject `Future[T]` automatically
Currently, an async proc looks like so:
# Return int
proc a(): Future[int] {.async.}
# Return void
proc b(): Future[void] {.async.}
# Return void, take 2
proc c()] {.async.}
There is an asymmetry in void which is confusing because there are two ways to do void
return, one of which implicitly injects the future and the other that doesn't.
The second confusion comes from the similarity of async and non-async implemenations - ie it's possible to create a function proc d(): Future[int] = ...
without using the async transformation and still fit it within the sync/async framework.
As such, I'd suggest that the async macro be changed such that it always automatically injects the future:
proc a(): int {.async.} = ...
proc sameAsA(): Future[int] = ...
Regarding backwards compat, the macro can detect the future and give a deprecation warning for a transitionary period of time.
This is very bad idea, because all the implicit conversions of target procedure arguments or return value can create confusion when reading compiler's errors.
Compiler will show you types which was converted by "macro code", while you do not see this conversion in your source.
For example:
proc a(): int {.async.}
got transformed implicitly to proc a(): Future[int]
and now all compiler errors will be about Future[int]
value, but not about int
.
So current small inconvenience will become much more annoying. As an alternative we can remove silent conversion of procedures without return values to Future[void]
and show an error.
compiler errors
isn't this something that the macro could help with? basically, could it output did you forget "await"?
in the right places?
if the objective of async/await
is to make async code look like sync code (which most async frameworks seem to want to do), then decorating things with Future
works against that goal - specially if you want to introduce other types like tuple
and Option
, it quickly gets very noisy - things that you would "naturally" do in sync code become very verbose.
I support this change. The same question was discussed in a Nim RFC and someone correctly pointed out that you cannot avoid the type discrepancy - it will be either present in eyes of the caller or in the eyes of the implementer:
proc foo(): Future[int] {.async.} =
result = 10 # How come the result is not a `Future`? That's what the signature says!
In the end, Nim has code generation features and we must learn to accept that. What this means it that when you start using a library, your first step should be to read the published documentation. You have to be aware that certain features such as custom pragmas may affect the generated code and you must develop the Nim survival skills to deal with this. I can promise you that the tools at your disposal will become better in the future.
There is a particular objective measure that gets improved with this change. It reduces the erroneous "degrees of freedom" of the API. It's no longer possible to produce certain compilation errors (oh, I forgot to add the Future
type).
Its very easy to remove all this result
replacements and force people to use complete
and fail
explicitly. And i really like such change, but i really dislike proposal to perform implicit conversions and produce many nonsense compile time errors, because compiler will start mention Future[T]
but you will not see Future[T]
in your procedures and so for you this errors messages will have no sense.
@arnetheduck because async
macro is untyped we can't get type information, and becasue we do not have type information its very hard to show meaningful error. And we are not talking here about await
in right places we are talking about modification of procedure's return value.
how hard would it be to introduce an experimental macro that does this (async2
or whatever)? ie to play around with the syntax without committing to it..
@arnetheduck all the logic of result value happens here in asyncmacro2.asyncSingleProc
.
This part is responsible for verification of return value:
https://github.com/status-im/nim-chronos/blob/master/chronos/asyncmacro2.nim#L164-L181
This part is responsible for creation of internal result Future[T] object.
https://github.com/status-im/nim-chronos/blob/master/chronos/asyncmacro2.nim#L187-L193
And last transformation which transforms proc foo() {.async.}
=> proc foo(): Future[void]
.
https://github.com/status-im/nim-chronos/blob/master/chronos/asyncmacro2.nim#L255-L259