>But now you are in the "goto" mess that this article describes.

This is where I don't really agree with the article. I agree with you that async/await may be better for UI programming - Go, and Erlang, which has a similar runtime to Go, aren't used to make frontends.

The article presents this example of 1 mainthread spawning 3 shortlived parallel tasks, waiting for them to complete, before they continue. This he argues, creates spaghetti code, where the concerns of a single routine are split among 3 different functions. This is the spawn/join model, and while it exists in Go, this isn't how concurrent go programs are normally written.

Goroutines are more commonly used like actors - in that a single goroutinThe concept of "branching off or onto" the main thread doesn't really exist. Instead my main thread runs something like an event loop, and in your example, the OnButtonClick handles the event synchronously, in its own goroutine, and will send a message back to the main thread's event loop on whatever state needs to change. This is the idea behind the CSP/Actor model which has been proven to scale for years - Erlang/OTP is a major proponent of it and is over 30 years old, and proven to scale (ex. Whatsapp managed 900M uses with only 50 engineers). If this model was as bad as goto, I don't think Erlang would have the reputation for building concurrent & parallel software it has today. This Actor model also isnt something that I can easily grok into the Trio library - and I'm hesitant to call something like Trio superior when the Actor model has years of experience.

Each model has their own pros and cons. A major plus of the Go model vs async/await is that I don't have to think about writing "asynchronous" code. I can write my code any way I like, and the Goruntime can easily make it asynchronous (partly because the Go language has already done the hard parts - everything, like sockets and files are async by default). This isn't true of the major async/await languages - in Javascript, everything that might be blocking has to either use callabacks or the async keyword. In Python, if I use a library that doesn't use my async library or is synchronous I lose. And Rust, it's already starting to rear its head as if I'm writing a library using Tokio, and I want to include a library using Rayon, I'm going to have problems. You could see why the designers, who thought they were building a systems language, would be wary of exposing an async runtime. Async runtimes "infect" everything around it.

However, a major plus async/await model is that since everything is async, its very easy for very small functions to be completely non-blocking. In Go if you had set of serial functions, they would always execute serially. For example if a request came into a web server, it would get its own goroutine. Then that goroutine would read from redis and then mysql. Most gophers would write the code such that the read from mysql would only happen only after the read from redis. You could put both reads in a goroutine - but async/await is much more efficient here, requires much less lines of code, and wont require a mutex. In async/await the two database reads will almost always execute parallely, without the developer having to do anything.

Each model has its own strengths and weaknesses which is why it's hard to consider one strictly better for another.