9 ms·
Go Statement Considered Harmful (2018)
- lacker 6y agoSo here's an example that I'm not sure how it would work with nurseries. Let's say I'm writing some sort of logging functionality for a larger codebase. The API in my library is `log(message)` where message is some random message object. It just appends the message to a file, but if the file size has gotten large enough then it does some slow work, maybe it batches things up and sends them off somewhere or it rotates the log files or whatever. So in normal Go, I can make this log function do some slow IO, call `go log(message)`, and I'm set. Or I can make the goroutine created inside the log function when it's needed, either way. With nurseries, where do I create a nursery? I don't want the code that calls log to have to wait for the logging to complete. So the caller shouldn't own a nursery and pass it to the logging library. The logging library can't create a nursery when it needs to do the occasional slow operation because the caller will have to wait for that too, if I understand correctly. So, do I need to just use some other abstraction entirely, or how would I implement this with nurseries? This almost reminds me of using Java threadpools... you can more or less do anything you want to do with them, you just need to create lots of queues and consumer objects and it takes several abstractions to implement what could just be a single abstraction.
- geofft 6y agohttps://stackoverflow.com/a/48300537 https://stackoverflow.com/a/48300537 > One of Trio's weirdest, most controversial decisions is that it takes the position that the existence of a background task is not an implementation detail, and should be exposed as part of your API. On balance I think this is the right decision, but it's definitely a bit experimental and has some trade-offs. There's some argument in there about why that's the case - the high-order argument, I think, is that when I'm done calling some function that uses your logging library, I might be surprised at some later point by seeing my code inside your logging setup. (Maybe the log server dropped the connection and now you're doing a CPU-intensive SSL handshake to reconnect to it, or something.) I think the idiomatic way to do this would be that, yes, the code that calls log() should provide a nursery somehow, potentially by asking its own caller to provide a nursery. There's an example in that SO answer of a class that accepts a nursery in its constructor, plus a convenience wrapper that creates a nursery and uses the class once (and then waits on tasks to complete before returning). If you really, really want the operation to happen in the background, there is https://trio.readthedocs.io/en/stable/reference-lowlevel.html#trio.lowlevel.spawn_system_task https://trio.readthedocs.io/en/stable/reference-lowlevel.htm... , but it's bad style to go sticking random things there. At the very least, document this as part of your API.
- neolog 6y agoFilesystem IO https://trio.readthedocs.io/en/stable/reference-io.html#asynchronous-filesystem-i-o https://trio.readthedocs.io/en/stable/reference-io.html#asyn...
- jayd16 6y agoYou could implement the log calls as channel writes. Then you could have a long running log writer task read from the channel in a similar way to the accept() example? Edit: After reading the docs it seems like you make a logging nursery and then your log calls throw tasks into that nursery?
- dragonwriter 6y ago> With nurseries, where do I create a nursery One option would be to have main() create and pass the nursery when it initializes the asynchronous logger. main() would have to wait for all logging to complete, but nothing else would.
- Spivak 6y ago> main() would have to wait for all logging to complete Which is presumably the desired behavior anyway as all objects fall out of scope during program termination.
- dang 6y agoDiscussed at the time: Notes on structured concurrency, or: Go statement considered harmful - https://news.ycombinator.com/item?id=16921761 https://news.ycombinator.com/item?id=16921761 - April 2018 (230 comments)
- int_19h 6y ago> You can't break out of one function and into another, and a return can take you out of the current function, but no further. Whatever control flow shenanigans a function gets up to internally, other functions don't have to care. Smalltalk, Ruby, and other languages in a similar vein beg to differ. And I don't think you could call e.g. Smalltalk "unstructured", either. On the whole, I'm not really convinced by the arguments presented there. Most async code is effectively linear on any given code path, and thus you don't need any abstraction other than tasks/futures to synchronize. When multiple concurrent tasks are spawned, ideally, the facility that does so should bind them together in a single task that can then be awaited (or not, if necessary). This takes care of resource cleanup, error handling etc.
- dragonwriter 6y ago> Smalltalk, Ruby, and other languages in a similar vein beg to differ. And I don't think you could call e.g. Smalltalk "unstructured", either. Smalltalk, Ruby, and any language with callcc is nearly as unstructured as it would be with goto (though most of them idiomatically minimize usage, which mitigates this in practice), which is also a part (implementation/optimization prospects are another) that Ruby banished continuations from the core language to an MRI-bundled library starting way back with 1.9, replacing the main uses with the more structured Fibers.
- dan-robertson 6y agoCallcc is pretty unstructured. Delimited continuations are a bit more structured and, to me, seem a useful primitive even if application code shouldn’t ever use them. Common Lisp has lexically scoped goto/return which can be useful. Example: (defun foo-find (foo x &key (test #'eql)) (foo-iter foo (lambda (bar) (bar-iter bar (lambda (y) (when (funcall test x y) (return-from foo-find y))))))) Some people try to implement something similar using exceptions but this only works if nothing swallows your special exception on its way up to the block you’re trying to return from.
- dragonwriter 6y ago
- rob74 6y agoIf you use a Dijkstraesque title like this one, you should either be a recognized expert on the subject yourself or at least cite some articles that support your statement. Using it to promote your "better" concurrency library smells like clickbait... EDIT: Ok, I stand corrected, this seems to be an influential article. But the title still smells like clickbait to me (especially because it singles out a well-known statement of a well-known language that has concurrency as one of its main selling points when it actually refers to using all types of threads - "green" or not)...
- kasperni 6y agoWell, so far the article is probably the main reason why structured concurrency is being added to both Java and Kotlin.
- platinumrad 6y agoYeah this random blog post from 2018 is definitely why Oracle started Project Loom in 2017.
- kasperni 6y agoI didn't say it started Project Loom I'm saying this article is one of the main reasons structured concurrency is being made part of project Loom. A link to this particular article have literally been on every project Loom slide coming out of Oracle for the last two years. You can watch Ron Pressler talk about it himself here [1]. [1] https://www.youtube.com/watch?v=lIq-x_iI-kc&t=14m30s https://www.youtube.com/watch?v=lIq-x_iI-kc&t=14m30s
- deleted 6y ago[deleted]
- mrkeen 6y ago> you should either be a recognized expert on the subject yourself Did you just request the Argument from Authority fallacy? > at least cite some articles that support your statement This is the article. Others can cite it.
- kasperni 6y agoThe wikipedia entry for structured concurrency provides a good intro into the subject [1]. Java in process of adding support for it as part of project Loom[2]. Kotlin has already based their coroutine support on it [3]. It is also on the roadmap of Swift [4]. [1] https://en.wikipedia.org/wiki/Structured_concurrency https://en.wikipedia.org/wiki/Structured_concurrency [2] https://wiki.openjdk.java.net/display/loom/Structured+Concurrency https://wiki.openjdk.java.net/display/loom/Structured+Concur... [3] https://elizarov.medium.com/structured-concurrency-722d765aa952 https://elizarov.medium.com/structured-concurrency-722d765aa... [4] https://forums.swift.org/t/swift-concurrency-roadmap/41611 https://forums.swift.org/t/swift-concurrency-roadmap/41611
- vlaxer 6y agoThe Wikipedia entry is pretty low on facts and historical context. In the previous discussions many people have disputed the originality of the approach that the Wikipedia entry seems to claim.
- eru 6y agoI've used both `trio` and `asyncio` in Python recently. Trio's nurseries seem to make error handling easier.
- zinclozenge 6y agoI like trio a lot, wish there was a grpc library with typing hints that could run on it.
- eru 6y agoYes, that would be neat. Trio's nurseries remind a little bit of IO in Haskell. Forgetting all about Monads etc, you can re-interpret IO-typing in Haskell as the requirement to keep carry something like a 'nursery' around with your code. Parts of code that don't have access to such a 'nursery'-like object can't do IO. (And because it's Haskell, that access is also tracked in the types.)
- samprotas 6y agoIt’s also very directly related the to “effectively standard” (but not included) async library in Haskell. https://hackage.haskell.org/package/async https://hackage.haskell.org/package/async The package description at the top of the link touches on the motivations which basically mirror this article. I’ve personally never reached for the built in forkIO. withAsync or it’s helpers like mapConcurrently are always equally capable, easier to use, and with none of the foot guns.
- Laremere 6y agoI started reading this article strongly disagreeing with it, but am convinced to the point that I would really like to try this in practice. My initial disagreement was centered in that nurseries seemed overly optimized for the "do a bunch of processing in parallel and then return a result" use case. This seems nice, but not necessary as Go's channel and waitgroup privatives solve this easily enough. My concerns were around use cases that I can initially think of, which don't fit this easy mold: - Running servers, with connections handled on threads (eg, typical Go http.ListenAndServe). However this works with nurseries very well (when it doesn't with, eg, parallel do). I expect that if I have that server run, it will only return with an error or having been stopped, and that all the pending connections will have been finalized. - Game engines: If I want to write a concurrent game engine, I don't want to be constantly creating and removing threads. Instead I want to spawn several well organized threads, some with their own tasks, others working from a shared pool. I want this processes to exist for the entirety of the time the game is running, so do I really need nurseries to manage cleanup and reuniting processes? That characteristic doesn't solve any significant problems. However, the statement that if you want to allow a function to run something in the background, you need to pass it an already opened nursery is convincing. This structures the main function to likely have a nursery created for the engine game engine process, and passed to the various systems which need to start background threads. This should make the important reasoning about carefully scheduling work clearer to look at and trace. - I recently worked on a project where we had a large volume of queries on fast changing data, with reasonably limited total data requirements. The solution was to have a background thread do the following: 1. Collect all waiting requests. 2. Update the internal cache with the state of the entire system. 3. In parallel let the various requests run their queries on the cached state. 4. Wait for the queries to finish, before restarting with #1. This enforces that requests always get state which is ordered after the request started (so create then query won't have a race condition), and is easy to reason about as the state of the cache cleanly alternates between being written by one thread, and read by many threads. Nurseries really don't help at all with this use case. However they don't harm it in any way either: The request threads are started by the server handling requests, which is blocking the main thread. The cache handling thread is started by the main thread before starting the server, which just works. The main thread keeps the nursery around, showing that if you looked at the main thread that a background task would be running there. So, all in all, I look forward to trying this style of concurrency.
- codeflo 6y agoI think this advocates a certain style of concurrency rather than a particular language feature. As hinted at in the article, nothing prevents me from spawning a global_nursery at the top of my entry point and making that visible everywhere. I can still use this global_nursery (or any other higher-level one) to break resource cleanup. I do find the argument compelling. But Python is simply too powerful for this abstraction to work perfectly, you’d need further restrictions. Maybe something like linear types (or Rust’s borrow checker) to make sure local state doesn’t escape into global nurseries?
- orisho 6y agoThere's one key difference between Go and all the other examples: an uncaught panic in any goroutine, not just the main goroutine, crashes the app. Often your main goroutine will just be starting other goroutines, in a multiple-services-one-process architecture, and it doesn't care whether they succeed or not, and its code need not be aware of whether they're critical. If they are, they should panic and crash the program. Just like a main function that crashes. Doing things with the nursery is restrictive which has its benefits, but I don't agree that it's just strictly always the preferred approach. I prefer an approach like Go's, where you can choose whether to use nurseries (errgroups), and the implementation for them is provided by the language authors. Sure, you are able to start and manage goroutines in this way, even if you don't care about their lifetime - as they are effectively individual programs, but you shouldn't HAVE to. So when you care about goroutines running only while their starter goroutine is at a certain scope, you can do so. But you don't have to, and when you don't need it - you don't need to pay the price. Go appears to take an approach that provides primitives that are as least restrictive as possible to make it easy to read code, but then also has a batteries included approach with its libraries.
- egeozcan 6y agoI have little experience in go, and didn't know about errgroups, thanks! Are "golang.org/x" packages something like an extended standard lib? https://pkg.go.dev/golang.org/x/sync/errgroup https://pkg.go.dev/golang.org/x/sync/errgroup
- evil-olive 6y agoYes, golang.org/x/ is of a "nursery" of sorts for things that are stdlib-ish but not fully baked enough that the maintainers want to support it under the 1.0 compatibility promise [0] https://golang.org/pkg/#subrepo https://golang.org/pkg/#subrepo > These packages are part of the Go Project but outside the main Go tree. They are developed under looser compatibility requirements than the Go core. 0: https://golang.org/doc/go1compat https://golang.org/doc/go1compat
- eqvinox 6y agoA SIGSEGV or SIGABRT (e.g. assert() failure) in any thread in C/C++ code will also crash the entire process.
- deleted 6y ago[deleted]
- eqvinox 6y agoOpenMP's [0] #pragma parallel directives function very much in the same way. The following code: #pragma omp parallel for for (size_t i = 0; i < limit; i++) whatever(); fully encapsulates the workload distribution and rejoining. By the time the code after the loop executes, all parallel execution of the loop is done. [0] https://en.wikipedia.org/wiki/OpenMP https://en.wikipedia.org/wiki/OpenMP, earliest versions dating to 1997
- Laremere 6y agoThere are important distinctions between this code and what it proposed by the OP. See the sections https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/#nurseries-support-dynamic-task-spawning https://vorpus.org/blog/notes-on-structured-concurrency-or-g... and https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/#there-is-an-escape https://vorpus.org/blog/notes-on-structured-concurrency-or-g... These differences elevate this proposal from handling parallel computation (something that is fairly easy to reason about with normal go routines anyways), to something that actually handles concurrency use cases in general.
- eqvinox 6y agoI was simply making an observation on what the control flow looks like on a source code level, nothing more and nothing less. OpenMP is, as you say, designed for computation, hence the directives it provides. The control flow is still majorly different from go routines, and the argument for this source code expression is IMHO the quintessence of the article. There simply is less to reason about if you have these constructs. With a go routine you still need a join somewhere, which makes it "unstructured" like gotos.
- conradludgate 6y agoYet another comment about rust... :) They mention RAII as an alternative to the `with` statement, and with Rust's borrow checker and strict memory management between async tasks, this isn't such a big problem there. A task must have a guarantee that any data they use will exist during the entire time the task needs it. Async in rust is already a very contentious topic though, much like this. I do find `go` statements tricky to reason about sometimes, and even the question of where to put them. Should `program.Start()` spawn the goroutine itself, or should you be expected to handle that. I've used both patterns in my work, but communicating that is very tricky
- jamesgreenleaf 6y agoI considered making some joke about how "considered harmful" essays should be considered harmful, but then I found that I am twenty years late. https://meyerweb.com/eric/comment/chech.html https://meyerweb.com/eric/comment/chech.html
- yccs27 6y agoIndeed. For most of the article, the conclusion could be "concurrency is hard" instead of "go is harmful".
- neolog 6y agoThe name is a play on the classic "goto" essay.
- ufo 6y agoMore importantly, it's directly referencing the contents of Dijkstra's essay and arguing that "go" is bad for the same reasons that "goto" is. In most of the other considered harmful blog posts the title is just a meme.
- chsasank 6y agoSee this for how it actually works in code: https://chsasank.github.io/concurrent-programming-trio-tutorial.html https://chsasank.github.io/concurrent-programming-trio-tutor...
- Blikkentrekker 6y agoHow can this work: asyncio.create_task(myfunc()) # Python with asyncio Surely it would then call `myfunc` in the main thread, block until it be done, then return whatever value it returns if it return at all, and call `create_task` with that value in the event that it return?
- incrudible 6y agoIt is implied that myfunc returns an awaitable object, not its ultimate result.
- pansa2 6y agoIIUC if `myfunc` is an async function, calling it will immediately return a coroutine object. None of the function’s code will run until it is `await`ed. (Unlike some other languages, async-await in Python is not “synchronous to the first await”.)
- giltayar 6y agoIf anybody's interested, I built an implementation of Nurseries in JS about two years ago, and have been using it here and there: https://www.npmjs.com/package/nursery https://www.npmjs.com/package/nursery
- bradhe 6y agoI think "considered harmful" is a little strongly worded. There's some conclusions the author jumps to to support their point explicitly in the titular section of the article.
- dangerbird2 6y agoIt appears to be more of a homage to "goto statement considered harmful" paper than it is a statement on the "harmfulness" of the `go` statement, as the author comparing `go` with `goto` and talking about Dijkstra's paper.
- dan-robertson 6y agoThis is somewhat similar to the idea of linear futures where a type system is used to ensure that each future is used exactly once. This still allows the kind of structure suggested in this article but with slightly different constraints. One issue is that linear futures don’t really allow for a select-like operation which takes some futures for a value of type T and returns a single future for a clue of type T that becomes determined to the same value as on of the input futures (roughly the one that finishes first but slightly undefined). A question for this proposal (or linear futures): how do you implement a time out? That is, a future that either becomes determined to some input future, or some error value if the input takes too long. The other big issue with joining control flow in async systems is error handling: when you join two threads, you can get two successes, one error, or two errors. When you select between two threads, you can get a success and, later, an error. Typically you want to pass the first error on eagerly. But where to? Do you pass it to something depending on the result of the future, or something from the dynamic scope when the future was created, or something else? And what happens with the second error? It might be bad to ignore it but it might be too late to care also. And the place where you might want to pass the error could be long gone by now.
- NoodleIncident 6y agoI think the article handles both cases (timeouts and the first error) by cancelling the other running async operations as quickly as it can.
- IHLayman 6y agoI liked the article, but I don’t understand how it handles interrupts? Wouldn’t signal handling imply a global nursery? Another discussion discussed logging, which would also imply a global nursery. If a SIGHUP hits the process, how does the signal handling thread in the global nursery cleanly kill the logging thread? I like the direction, but maybe there have been refinements in the last two years to handle these cases, because they seem unresolved/unresolvable.
- bionhoward 6y agoI wrote one of these "considered harmful" papers about the way Terraform passes ID / ARN / NAME / thing_name, policy.id, yadda yadda etc etc (a different way to refer to each resource from each other resource yields n^2 different ways things could be wired up) and I regret it. Seems a bit negative and pointless and I wish I hadn't written it. I'd rather just say, hey, if Terraform did "pass by reference" we could type less and implementers could take the data they need instead of devs needing to figure out which of 4 different IDs to pass for each of 4 different references for 20 different resources TLDR: Dont write a considered harmful paper. It's a trap. Write about how to make stuff better, not about how stuff is bad!
- zzbzq 6y agoDijkstra's paper is hard to read. I've gone through it and broken it down. It turns out, the central argument put forth in Dijkstra's paper is that the 'goto' statement messes up the stack trace. Although I don't think the term "stack trace" existed yet, so he keeps talking about "textual indices." The 'go' statement actually allows the cleanest stack traces of any language I've encountered. Go crash dumps can print 40 stack traces for 40 threads and still be shorter, cleaner, easier to read than the stack traces of something like a single line of C# async/await code. The article seems to argue that it's hard to run more code after the completion of a goroutine. But, the entire point is you don't have to. Go does async i/o under the hood, so you just write the lines of code sequentially in the order you want them to happen, and they will happen at the right time. As a side note about the 'goto' statement, modern languages, if they have the statement at all, explicitly lock 'goto' usage into one stack frame anyway. I'm 100% unconvinced there's anything wrong with it in those instances. Anything bad things you can do with it can be done wrong as easily with other control flow.
- goto11 6y agoDijkstra is not talking about stack traces. Structured programming is not just about functions, it is also control structure blocks like if or while-blocks instead of goto's, which does not affect stack traces. Dijkstra is talking about describing and analyzing the execution of a program. With structured programming, the execution of a program can be described as a tree of executed blocks. A program using goto's is a tangled graph, since every point can jump to any other point. This makes it much harder to describe and analyze. I doubt Dijkstra cared much about stack traces, since he didn't care for debugging at all. For him, programs was something you analyzed on paper and proved correct. As for gotos being constrained to a single function, this does not make any difference for Dijkstras point, since a single function can still contain the same complexity as a whole unstructured program.
- Banana699 6y ago(Imperative) Programs cannot be pure trees, looping constructs necessarily introduce cycles. What structured programming is all about is to make those cycles as well-behaved as possible. You are largely right nonetheless, Dijkstra's argument could be summarized as: 1- Understanding code depends on imagining it's dynamic effects from its static description 2- As we progressively make code more powerful (executing single statements in sequence -->+ executing either of two statements depending on the current state -->+ aggregation of statement blocks into subroutines -->+ looping constructs); the job of the entity reading your code becomes progressively harder as it tries to reason about: a List -->+ a Tree -->+ a generalized extra-recursive tree where nodes are themselves trees -->+ that last kind of tree but with cycles. 3-GoTos create especially-nasty uber-unpredictable networks of cycles while adding no more power than other well-behaved options. The essence of the argument is a tale as old as time: increasing the Freedom of Expression for the writer increases the Burden of Understanding on the reader. Stack traces, in a generalized kind of way, is another equivalent form of describing Dijkstra's argument. The previous way of explaining Dijkstra's argument imagines an idealized reader (could be an actual person, a static-analysis tool trying to predict some property of your code, or any other "reader") and asks you to consider the complexity of the data structure it tries to construct to mentally represent your program in static form. Now imagine it the other way: the reader is seeing your program's dynamical behavior in a "debugger" (could be an actual debugger, could be a sheet of a paper with a bunch of trace tables,...), how does the amount and complexity of the data structures that this idealized reader needs to follow the behaviour of your code scales as the complexity of your code scales? For purely sequential programs , that 's just a single counter keeping track of where we're in the program . Add conditionals, and you now have to keep track of a flag indicating for each line which branch was taken. Add blocks or subroutines , and you are now maintaining a whole stack of such counters/flags. So far, so linear, you are still limited to incrementing counters, raising flags, and adding or removing counters/flags from the top of the stack. But you're never decrementing counters (going back to a previous point in execution) or touching the stack in the middle. Gotos and loops destroy this: Gotos are the worst of the pair as it arbitrarily jumps you to any counter on the whole stack without even recording the point of return. The whole concept of an "execution stack" becomes meaningless, program execution is a graph that you maintain in your head (or in the debugger) as it threads through the code. Other looping constructs are more well-behaved in that they state beforehand that a jump will happen and also the fixed whereabouts of its destination. The argument is that since different constructs give you the equivalent power but complicate your code's dynamic behavior asymmetrically, you should choose some of them and ban others. The first version of the argument imagines code understanding as a writer-reader dynamic and talks about the reader reasoning about the static form of the programs written by the writer, the second imagines it as a dancer-viewer dynamic and talks about the viewer following the flow of the program orchestrated by the dance choreographer (the programmer). Two ways of saying the same thing.
- wskinner 6y agoI was surprised to reach the end of the article and find that Trio is just a python library. Doesn’t that mean that even if you use Trio, if you also use any other libraries, you still cannot be sure about the concurrency properties of your code unless you read the transitive closure of your dependencies? I expected Trio to be a new language which enforces nursery usage in the compiler.
- jdc0589 6y agoI believe that is correct, unless it does some crazy monkey patching or something (which would be horrible). sounds just like some of the original problems with the python event-loop webservers: everything was great until you used something that wasn't great.
- dnautics 6y agoThe erlang case suffers from none of these problems, plus it's even more sophisticated, you can set up failure domains with few lines of code; it's designed for the spawn to be truly autonomous with restart/retry logic, do rpc over distributed systems with unreliable networks, etc. Once you've spent time working in erlang concurrency, it's hard to believe that this pattern is an advantage except for the fact that python (and go, and js) is designed for linear primary execution flow. In erlang by contrast, a script with a primary flow is a special case of a concurrent system (which itself is a special case of a distributed system). Addendum: If I'm not mistaken, the nursery concept does exist in the BEAM vm, it's Task.await_many in elixir.
- systemBuilder 6y agoJust what's needed! Another code Nazi telling everybody the "right" way to program because they have seen it all, done it all, and now, finally, know it all! At least he put in a library unlike the Go designers who welded their concurrency primitives in stone!
- makkovjulia 6y agoI do not think that's true.
- vvern 6y agoThis is timely. Cockroach has recently decided to lint against the go statement. We also don’t like errgroup because its context propagation is implicit. See https://github.com/cockroachdb/cockroach/pull/62243 https://github.com/cockroachdb/cockroach/pull/62243.
- lacker 6y agoCockroach definitely seems like a case where you'd want to keep your design more conservative, putting in more effort and being stylistically more restrictive to reduce bugs and increase performance, compared to something like a stateless application that talks to many backend services and is not a big deal if it crashes occasionally.
- notacoward 6y agoThis seems like a very minor twist on fork/join, which only gets mentioned in a very snarky footnote. IMO it would be better to present it as such, instead of creating new terminology.
- Jtsummers 6y agoGo to the section "go statement considered harmful" itself. Specifically the subheading "Go statements break automatic resource cleanup". He explicitly mentions join in that section. Specifically, not all of the concurrency libraries or language constructs offer a join-equivalent, and when they do it isn't language mandated that it is used. It remains "unstructured" and requires, like with his analogy to goto, careful use and monitoring by the programmer rather than getting language/library behavior guarantees.
- thayne 6y agoAre there libraries in other languages that work similarly to Trio?
- Jtsummers 6y agohttps://trio.discourse.group/t/structured-concurrency-resources/21 https://trio.discourse.group/t/structured-concurrency-resour... Has a short list of library implementations for C, Python, Swift, and Kotlin. As I understand it (not a Java programmer, well not up to date on the ecosystem) it's being adopted/explored in Java via Project Loom as well.