Golang does have an excellent built-in data race detector, which shows exactly which threads (and functions) are colliding.
https://go.dev/doc/articles/race_detector
Caveats are that it requires cgo, and it might have lower performance, so enable it during development but not during production. Also detection requires actually triggering the data race, so you need good tests to try to exercise those areas of code.
Although Rust prevents them outright, that does seem to come at the expense at some flexibility, and the actual security risk of a data race in Golang seems to be moot. For example, if you know that a shared object will only be used when the program is starting up and in single-threaded mode, then you can safely use it then without a mutex.
Even when Golang does have a data race, the race causes the program (or at least the thread) to panic and thus is probably not directly exploitable, unlike older languages.
In other words, golang changes the risk of a data race from being a vulnerability to just a program-crashing bug. [EDIT: please see comments below which indicates that this may not be true in all cases, especially if you're trying to sandbox untrusted code!]
Beyond that, Go provides special capabilities (channels) to allow communications across goroutines (which are analogous to threads but can also automatically use different cores, unlike regular threads). Channels are like a vastly improved version of Erlang's mailboxes, and are the best way to pass information between goroutines.
So, rather than use mutexes, channels are Golang's idiomatic way to eliminate data races entirely. You can pass any complex object through a channel to another listener (blocking or non-blocking) on the other side. This excellent video is actually what convinced me to switch to Go for concurrent coding (which is the only place where you run into data races anyway):
https://go.dev/blog/waza-talk