I mean, even understanding the primitives and having worked with them for a while still doesn't stop me from being frustrated when reasoning about a random piece of concurrent code.<p>In everyday apps, I often use unbuffered channels and errgroups so I more or less understand where each goroutine starts and exits, but it's much more complicated with multiple buffered channels, for example.<p>I wish I could reason about concurrent code as well as Rob Pike in his talk on Concurrency Patterns :)<p>How did you get confident when writing concurrent code in Go?
I'm going to challenge the premise of the question and say no one should be "comfortable" writing concurrent Go code.<p>Maybe no one should be comfortable writing concurrent code, period. Concurrent code is hard to reason about, and when bugs occur, they are hard to reproduce and debug.<p>When I first discovered Go's concurrency model, I thought, "This is great! It has a nice message-passing system in the form of channels that people can use to write reliable concurrent code."<p>But as I got into it, I realized, no, not so fast. Go does provide a message-passing system, but it does nothing to prevent you from sharing memory between your goroutines, inviting all the problems such as race conditions and data corruption that that entails. Furthermore, if you don't design your goroutines such that when you represent them as a graph, the graph is acyclic, then you have potential deadlocks when your program runs. And you might think it's not hard to design the system so there are no cycles, but even something as simple as two goroutines where one sends a message to the other and the other wants to send a message back -- boom! you've got a cycle. And any time you have cycles in your graph, you have to think long and hard about where to add buffering to your channels and how much.<p>I remember reading somewhere that in Go, people use concurrency 12x more than in other languages. I was pretty horrified by that! The makers of Go made concurrency easy -- maybe <i>too</i> easy. People use it everywhere.<p>I have not yet had the time to look into Rust and its borrow-checking system, so before you ask I can't comment on that. What I can say for the moment is that the Go programming language is exceptionally well-designed and well implemented in just about every regard except its concurrency model. The concurrency model makes concurrency <i>easy</i> but does not make it safe. It is dangerous in Go and you have to be ultra-ultra-careful when you write concurrent code in Go.