I'm not saying it's impossible. I'm saying that the process will be incredibly intrusive. To support both synchronous and asynchronous models you often need to go through contortions to split out as much common code as possible. That's because you can only call async functions using specialized await invocations. See http://journal.stuffwithstuff.com/2015/02/01/what-color-is-y...

Alternatively, you need to make one or the other a 2nd class citizen (e.g. write the core implementation using async/await and make the synchronous API a wrapper), making that aspect of the implementation substandard in the eyes of your library users.

There are alternative language primitives, like asymmetric stackful coroutines, that provide much better semantics. You can continue sharing all your code using the same function composition patterns because there's no bifurcation in the function space--a coroutine looks and behaves and is invoked like any other function.

Stackful coroutines aren't memory efficient if you try to maintain compatibility with traditional C ABIs, especially on 32-bit platforms. To reliably run C code musl libc, for example, has minimum stack sizes of 80KB. By contrast, a stackful coroutine in Lua has an initial allocation size (including stack) of less than 300 bytes, and the stack is dynamically resized as needed. So stackful coroutines aren't a good fit for Rust which emphasize seamless backward compatibility with existing C code.

But for a language like Swift I think the designers could have gone the other way[1], and it's a shame they've committed themselves to a language primitive that is a dead-end and only as popular as it is because of extrinsic limitations--Rust's is backward compatibility with C, for JavaScript and Python it's the fact that the existing engines intertwine the C stack with the languages logical stack. (For use cases where async/await may be a preferable paradigm, e.g. code documentation or ability to block suspension of the caller, async/await can be implemented cleanly and cheaply atop the lower-level primitive. Stackful coroutines are a strictly more
elegant and capable primitive.)

People debate these issues endlessly and many people would disagree with me. But my point is that the real proof of my argument can be seen in the difficulty of authoring and maintaining real world libraries like SwiftTLS. Forget abstract arguments--look at the concrete use cases and not anecdotes or proofs-of-concept. The async/await world brings significant code duplication and decreased composability (i.e. ability to mix-and-match libraries and toolkits). It's a hack that is rationalized post hoc.

[1] While Swift needs to support C, C++, and Objective-C interoperability, Apple's long-term goal is a predominately Swift-based ecosystem. And all of Apple's hardware is now 64-bit, so theoretically they could even support full C ABI interoperability without an undo memory hit by sparsely allocating stacks and extending them in-place on demand. Ultimately, I think part of the calculus is that it's easier to fiddle with your languages type system than it is to fix the low-level stack management of a pre-existing compiler (i.e. LLVM) to support many dynamic stacks. Go could do this because they wrote (or rewrote) their own compiler toolchain from scratch. Scheme and Lua can do this because the runtime VM stack is kept distinct from the native C ABI stack used by the engine; they effectively multiplex many VM stacks atop a single native C stack. Similar story for Java's Loom project regarding JVM stack management.