| > This is not true for JS. JavaScript is much more constrained than Rust, because of the spec and the compatibility with existing code. The js VM has many constraints, like being single threaded or having the same GC for DOM nodes and js objects for instance. Rust could patch LLVM if they needed (and they do already, even if it takes time to merge) but you can't patch the whole web. > fact, inside some `read` method the runtime could even run an entire event loop while it waits for the IO to complete, just as `await` effectively does No it cannot without violating its own spec (and it would probably break half the web if it started doing that). Js is single threaded by design, and you can't change that without designing a completely different VM. > No, it isn't. JS isn't Haskell and doesn't track effects, and syncFoo can change GlobalState.bar. Of course, but if syncfoo is some function I wrote I know it doesn't. The guarantee is that nobody else (let say an analytics script) is going to mutate that between those two lines. If I use await, everybody's script can be run in between. That's a big difference. > because the generated code is virtually the same. You keep repeating that over and over again but that's nonsense. You can't implement stackless and stackful coroutines the same way. Stackless coroutines have no stack, a known size, and can be desugared into state machines. Sackful coroutines (AKA threads) have a stack, they are more versatile but you can't predict how big it will be (that would require solving the halting problem), so you can either have a big stack (that's what OS thread do) or start with a small stack and grow as needed. Either approach has a cost: big stack implies big memory consumption (but the OS can mitigate some of it) and small stack implies stack growth, which has a cost (even if small). |
You don't need to patch the whole web. V8 could compile stackful continuations just as efficiently as it does stackless ones. It is not true for Rust without some pretty big changes to LLVM.
> No it cannot without violating its own spec (and it would probably break half the web if it started doing that).
Yes, that's a backward compatibility concern. But just as you can't change the existing `read` and need to introduce `asyncRead` for use with async/await, you could just as easily introduce `read2` that employs continuations.
> Js is single threaded by design, and you can't change that without designing a completely different VM.
A single-threaded VM could just as easily run an event loop inside `read` as a multi-threaded one.
> Of course, but if syncfoo is some function I wrote I know it doesn't.
First, it can't entirely be a function you wrote, because it's a blocking function. It must make some runtime call. Second, this argument works both ways. If it's a function you wrote, you know if it blocks (in which case any effect can happen) or not.
> You can't implement stackless and stackful coroutines the same way.
Your entire argument here is just factually wrong. For one, all subroutines are always compiled into suspendable state machines because that's exactly what a subroutine call does -- it suspends the current subroutine, runs another, and later resumes (but you need to know how it's compiled, something V8 knows and Rust doesn't, as Rust runs on top of a VM). But even if you want to compile them differently for some reason, a JIT can compile multiple versions for a single routine and pick the right one according to context without any noticeable peformance cost.
For another, it is true that you don't know how much memory you need in advance, but the same is true for stackelss coroutines: you allocate a well-known frame for each frame, but you don't know how many frames your `async` calls will need. All that is exactly the same for stackful continuations. In fact, you could use the exact same code, and represent the stack as a linked-list of frames if you like(that's conceptually how async/await does it). There is just no difference in average performance, but there is more work. The allocation patterns may not be exactly the same (so maybe not recommended for Rust), but the result would be just as likely to be faster as slower, and most likely just the same, as async/await in JS, a language where an array store can cause allocations.