# Reading notes: Notes on structured concurrency, or: Go statement considered harmful https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/ ###### tags: `reading-club` Leave questions, observations, discussion topics below. --- ## `s/go/task::spawn` yosh: heh, while reading this I'm definitely mentally replacing `go` with `task::spawn` the whole way through. Funny because this post came out in 2018, but both futures-rs and tokio were first released in 2016. Which means we're still working on abstractions defined prior to the popularization of structured concurrency ideas. In a way it's maybe a blessing we've taken so long to get some of these things stable? eholk: Rust has other tools to deal with some of these issues, e.g. passing ownership of files. tmandry: Yes, and sometimes we punch holes through those, e.g. duplicate objects to a fd or arena/id-based APIs. eholk: Also hard to reason about the control flow, when things return etc ## different types of nurseries yosh: in Rust we have a difference between a "nursery" and a "handle we can spawn from". We see this in effect with the `thread::scope` API: - [`thread::scope`](https://doc.rust-lang.org/std/thread/fn.scope.html): the "nursery" which owns the threads, and is in fixed in-place until all tasks have finished - [`thread::Scope`](https://doc.rust-lang.org/std/thread/struct.Scope.html): which enables spawning new threads on and can be cloned. yosh: I suspect we may want to keep this distinction for a version of "async scope" as well: one thing which owns tasks, another thing which can spawn new tasks, and can be freely cloned and passed into functions. tmandry: `thread::scope` is analogous to the `with` clause in python yosh: Can we inject something like that to enforce run-to-completion semantics for your nursery future? ```rust await with scope = task::scope() { // we know `scope` is now fixed in place scope.spawn(async { ... }); } ``` ^^ tmandry: lightweight linear type/poll-to-completion mechanism? eholk: `.await` has run-to-completion semantics, it's when you "escape outside the monad" that you break that. Run-to-completion in the trait could help. ```rust match foo { x => async { let file = ... // drop(file).await }.await, y => await { let file = ... // drop(file).await } } ``` --- ## Lambda the Ultimate Goto eholk: I wonder what the analogy between structured concurrency and Lambda the Ultimate is? https://dspace.mit.edu/handle/1721.1/5753 (Tail calls are goto) The conversation then (1977) was that function calls were expensive -- paper was saying actually, we just implement them badly, and once you fix that they're just better. --- ## Happy Eyeballs Yosh: Eric and I made a cool variant on this with the `futures-concurrency` crate: [happy eyeballs example](https://github.com/yoshuawuyts/futures-concurrency/blob/main/examples/happy_eyeballs.rs). I suspect that "nursery" is not the only async structured concurrency control-flow possible, though it's probably the most generic one. In Rust we can create more specialized concurrency control flow constructs which may be less generic, but can in turn be more optimized. Such as `join` or `race`. It'd be fun to maybe write some of the flow graphs for `join` and `race`, etc. (: --- ## C Crimes > If we used goto to jump into the middle of our with block... what would that even do? eholk: Fun fact, in C this is legal: ```cpp goto label; switch(x) { label: case 0: case 1: } ``` This can lead to really weird behavior about whether variables are initialized or not. See also, Duff's Device: https://en.wikipedia.org/wiki/Duff%27s_device --- ## Time opportunity From the conclusion: > Unfortunately, **to fully capture these benefits, we do need to remove the old primitives entirely**, and this probably requires building new concurrency frameworks from scratch – just like eliminating goto required designing new languages. But as impressive as FLOW-MATIC was for its time, most of us are glad that we've upgraded to something better. Yosh: I don't know of a way in which we can retrofit Rust to disallow unstructured conctrol flow entirely. But we sure can decide not to ever add new control flow constructs which *aren't* structured. tmandry: Maybe we can adopt structured patterns in our interop abstractions, see if we can get the ecosystem to adopt. eholk: Analogy to safe and unsafe. You have to make good decisions within unsafe code, extra scrutiny