The introduction of generics should make it easier to wrap these lower level concurrency primitives into higher level safe constructs without needing code generation or runtime type casting via interface{}.
Most devs also struggle to structure go programs in a way that makes them easily testable, but that’s true in a lot of languages.
> The introduction of generics should make it easier to wrap these lower level concurrency primitives into higher level safe constructs without needing code generation or runtime type casting via interface{}.
That process has actually already begun with Go 1.19's sync/atomic.Pointer[T]. See https://pkg.go.dev/sync/atomic@master#Pointer. Things like easier non-blocking send and receive or easier fan-in and fan-out channels should probably follow suit.
My experience especially relative to Java is the opposite - between httptest and sqlmock and the ease of setting up listeners in a separate goroutine, it's easy even for new Go developers to create new, realistic request-to-response functional (behavioral / Detroit-style / whatever they're called today) tests.
On the other hand, getting our long-time Java devs to hook up a reasonable wiremock test is like pulling teeth. They much prefer piles of redundant unit tests with a few Spring-injected "integration tests" against H2 and an in-process gRPC "endpoint" or whatever, that of course therefore aren't testing much integration at all.
What makes go powerful also make it weak.
In general the complexity need to live somewhere and in go it can only be the code itself as the language is too simple.
As for concurrency, that topic is hard in pretty much any mainstream language. Maybe with the exception of Rust, but Rust isn't exactly a langauge that is not “hard to master”, and even Rust doesn't protect you from all forms of concurrency issues. Merely most of them, heh.