> One key question, if we disallow explicitly creating Fibers inside an async block, > can a Fiber be created outside of it and not block async, or would that also be excluded? Viz, this is illegal: >
Creating a `Fiber` outside of an asynchronous block is allowed; this ensures backward compatibility. According to the logic integrity rule, an asynchronous block cannot be created inside a Fiber. This is a correct statement. However, if the asynchronous block blocks execution, then it does not matter whether a Fiber was created or not, because it will not be possible to switch it in any way. So, the answer to your question is: yes, such code is legal, but the Fiber will not be usable for switching. In other words, Fiber and an asynchronous block are mutually exclusive. Only one of them can be used at a time: either Fiber + Revolt or an asynchronous block. Of course, this is not an elegant solution, as it adds one more rule to the language, making it more complex. However, from a legacy perspective, it seems like a minimal scar. (to All: Please leave your opinion if you are reading this ) > // This return statement blocks until foo() and bar() complete. Yes, *that's correct*. That's exactly what I mean. Of course, under the hood, return will execute immediately if the coroutine is not waiting for anything. However, the Scheduler will store its result and pause it until the child coroutines finish their work. In essence, this follows the parent-child coroutine pattern, where they are always linked. The downside is that it requires more code inside the implementation, and some people might accuse us of a paternalistic approach. :) > > should consider if async {} makes sense to have its own catch and finally blocks built in.) > We can use the approach from the RFC to catch exceptions from child coroutines: explicit waiting, which creates a handover point for exceptions. Alternatively, a separate handler like Context::catch() could be introduced, which can be defined at the beginning of the coroutine. Or both approaches could be supported. There's definitely something to think about here. > > That is still dependency injection, because ThingRunner is still taking all of its dependencies via the constructor. And being readonly, it's still immutable-friendly. > Yeah, so basically, you're creating the service again and again for each coroutine if the coroutine needs to use it. This is a good solution in the context of multitasking, but it loses in terms of performance and memory, as well as complexity and code size, because it requires more factory classes. The main advantage of *LongRunning* is initializing once and using it multiple times. On the other hand, this approach explicitly manages memory, ensuring that all objects are created within the coroutine's context rather than in the global context. Ah, now I see how much you dislike global state! :) However, in a scenario where a web server handles many similar requests, "global state" might not necessarily win in terms of speed but rather due to the simplicity of implementation and the overall maintenance cost of the code. (I know that in programming, there is an entire camp of immutability advocates who preach that their approach is the key remedy for errors.) I would support both paradigms, especially since it doesn’t cost much. A coroutine will own its internal context anyway, and this context will be carried along with it, even across threads. How to use this context is up to the programmer to decide. But at the same time, I will try to make the pattern you described fit seamlessly into this logic. Ed.