To use the articles example, there are typically multiple bank accounts you want to update atomically, so guarding one account with a mutex doesn’t help you prevent deadlocks. The lock needs both accounts. The Mutex<T> example just doesn’t work with interacting objects.
In C++ you'd want to still also offer std::mutex because C++ doesn't have Zero Size Types, so a C++ Mutex<T> equivalent would always need space to store something. Mutex<()> is the same size as a hypothetical "mutex only" type and so Rust has no reason to offer a separate type representing a mutex which doesn't protect anything in particular.
In fact even without actually putting anything of substance in the mutex, you can get value from type system judo using this mechanism, which C++ doesn't appear to do either.
Neither the Empty Base Class nor [[no_unique_address]] give C++ Zero Size Types. The [[no_unique_address]] attribute is a way to achieve something empty base classes were useful for without the accompanying problems, so that's nice, but it's not ZSTs.
Can you say whether you genuinely thought C++ had ZSTs? And if so, how you came to that conclusion ?
I'm not saying that C++ has zero size types. I'm saying that no_unique_address and EBO are a way to store a stateless object without it occupying any space, which is all you need to implement a zero space overhead Mutext<T> for stateless types.
I think the complexity to deliver an equivalent of Mutex<T> which also works via no_unique_address to deliver no-space-overhead for deliberately stateless types that would otherwise add 1 byte to the type size is probably a bit much to ask.
Thanks for pointing me to Boost synchronized_example<T> showing that this does exist, at least as an experimental library feature.
It is not exactly rocket science: https://gcc.godbolt.org/z/6Kz53bs7x. Bonus it supports visiting multiple synchronized at the same time, deadlock free.