The key problem with the “colored” article is that it’s a mix of two things: a description of a real limitation that JS has, and author’s preference for a unified implicit syntax. Rust doesn’t have JS’s problem, and has explicit syntax for a reason.
It’s possible to have an “async” system for systems programming that isn’t colored. Zig solved this problem. In the case of zig, I can write a single function to be called from the Erlang virtual machine’s FFI either in an async fashion to use it’s the vm’s preemptibility, or I can call the same function to be run in its own thread (this is non-async), so you can explore the tradeoffs without having to write your function twice.
I really like what Andrew did with color-free async in Zig, but it’s not a panacea. There are limitations. You can’t use multiple different event loops (one blocking, one non-blocking) in the same binary, for example. Unless I’ve misunderstood something.
You absolutely can use multiple event loops in the same binary. It isn’t obvious how to do it cleanly, if you ask me, though, or I haven’t hit upon “the correct pattern” for it
I’m not convinced that solves a real language problem since the caller has to opt in to the desired behavior and the callee has to support both modes of operation. It doesn’t allow you to just call an async function from a sync context and have it automatically work. Zig allows you to put both versions in one function, but now you don’t know which async functions you can safely call from sync without reading the documentation or looking at the implementation. Is there something I’m missing here?
Kernel multithreading is expensive, but even Go-style “green threads” have to have a separate stack for each green thread. Stacks are gnarly, because it’s unclear how much space should be reserved for them ahead of time. They have to dynamically adjust to the run-time demand, and when the original allocation is used up, you get a pause as you try to allocate more.
Does Rust async not allow unbounded recursion? This sentence implies that.
Rust’s async does not allow recursion directly. Call graph of async function calls translates to a struct representing their combined state/stacks, so a direct recursive call would make the struct infinitely large (struct Future { calls: Future })
You can call async functions recursively if you have to, but you need to insert a layer of indirection that heap allocates more space for another Future object (turning the state struct into a linked list). It extreme cases this can make polling cost exponential with the depth of recursion. Non-recursive calls are fine (they are flattened into the state machine) and loops are fine too (they have fixed stack usage).
It’s interesting that this scheme for bounding stack growth (so you can precisely preallocate it) is entangled with the “async system”. Being able to do that seems useful in other contexts too, without having to bring in any async or Futures machinery.
Rust language/standard library has no way of running async code. It only has this “calls to structs” translation, and leaves the rest of the async machinery to 3rd party libraries. So you’re free to abuse it for non-async purposes if you want to :)
The key problem with the “colored” article is that it’s a mix of two things: a description of a real limitation that JS has, and author’s preference for a unified implicit syntax. Rust doesn’t have JS’s problem, and has explicit syntax for a reason.
It’s possible to have an “async” system for systems programming that isn’t colored. Zig solved this problem. In the case of zig, I can write a single function to be called from the Erlang virtual machine’s FFI either in an async fashion to use it’s the vm’s preemptibility, or I can call the same function to be run in its own thread (this is non-async), so you can explore the tradeoffs without having to write your function twice.
I really like what Andrew did with color-free async in Zig, but it’s not a panacea. There are limitations. You can’t use multiple different event loops (one blocking, one non-blocking) in the same binary, for example. Unless I’ve misunderstood something.
You absolutely can use multiple event loops in the same binary. It isn’t obvious how to do it cleanly, if you ask me, though, or I haven’t hit upon “the correct pattern” for it
I’m not convinced that solves a real language problem since the caller has to opt in to the desired behavior and the callee has to support both modes of operation. It doesn’t allow you to just call an async function from a sync context and have it automatically work. Zig allows you to put both versions in one function, but now you don’t know which async functions you can safely call from sync without reading the documentation or looking at the implementation. Is there something I’m missing here?
Does Rust async not allow unbounded recursion? This sentence implies that.
Rust’s
async
does not allow recursion directly. Call graph of async function calls translates to astruct
representing their combined state/stacks, so a direct recursive call would make the struct infinitely large (struct Future { calls: Future }
)You can call async functions recursively if you have to, but you need to insert a layer of indirection that heap allocates more space for another
Future
object (turning the state struct into a linked list). It extreme cases this can make polling cost exponential with the depth of recursion. Non-recursive calls are fine (they are flattened into the state machine) and loops are fine too (they have fixed stack usage).Fascinating!
It’s interesting that this scheme for bounding stack growth (so you can precisely preallocate it) is entangled with the “async system”. Being able to do that seems useful in other contexts too, without having to bring in any async or Futures machinery.
see:
tooling to statically calculate stack usage for non-recursive programs
termination proofs for recursive functions in ats
Rust language/standard library has no way of running async code. It only has this “calls to structs” translation, and leaves the rest of the async machinery to 3rd party libraries. So you’re free to abuse it for non-async purposes if you want to :)