I hold the opinion that the async/await programming paradigm as implemented by Python (and Nim) is not good enough for a modern language.
1. using async everywhere may be pollution This is a snippet of an asynchronous code: import asyncdispatch import asyncfutures import sugar import sequtils proc doIo(faux_io: int) {.async.} = await(sleepAsync(faux_io * 100)) echo faux_io proc doAllIos(ios: seq[int]) {.async.} = await(all(collect((for i in ios: doIo(i))))) waitFor(doAllIos(toSeq(1..10))) Run My opinion is that the intent is too much polluted with the execution flow. Having `waitFor` and `await` symbols in the code makes it unnecessary complicated. In non async programming, calling a function that may block is not done with a `block Foo()` expression, we just call Foo() directly, having to use `await` is an implementation detail IMO that shouldn't be exposed to the user. This is a code that I could come up with that is verbose only on the least statistically used use case: proc doIo(faux_io: int) {.async.} = sleepAsync(faux_io * 100) # always awaits echo faux_io proc doAllIos(ios: seq[int]) {.async.} = for i in ios: defer doIo(i) # doIo() will now be collected as Future # and deferred to be awaited on descope doAllIos(toSeq(1..10)) Run 2. calling blocking code from async function could be an error proc doIo(faux_io: int) {.async.} = sleep(1000) # this blocks, likely this is an error Run Calling a blocking procedure from an async procedure is an error in 99% of the cases, the compiler should warn or error on this unless explicitly told so by the user, i.e: proc doIo(faux_io: int) {.async.} = block sleep(1000) # this blocks, likely this is an error Run I am fully aware of the technical challenges of achieving such syntax, and that such syntax brakes the compiler forcing it to be async aware, but I believe this conversation is important for a language aiming to be modern and expressive.