It's a poor fit for this role, even if it is in practice what we have available today.
One of my favourite examples is volatile. Volatile is more crazy in C++ but it's pretty crazy even in C. In both cases the standard basically shrugs, "Hope you know what you're doing because we sure don't" and offers no real insight into what this feature promises to do for you. But there is no other mechanism provided for MMIO.
Think of Rust's std::ptr::read_volatile (and write_volatile). These intrinsics do the thing you actually wanted (reading, or writing, a fixed size blob of "memory" that presumably wasn't really just RAM) and thus are important for writing device drivers and so on with MMIO.
[ You may be thinking, "But I need the correct size of blob read or written or my driver won't work", Rust has generics, so these functions are generic over the integer type you're reading/ writing, if you read a u64 that's a 64-bit read, if you write four u8s that 4 x 8-bit writes and so on ]
But C's volatile is a type qualifier instead. Why? Would it mean anything to, for example, integer divide an MMIO fetch by fifteen and write it back? a/= 15; No. So then why make it a type qualifier? When volatile was added to C they'd only just invented simple optimisations like re-ordering so they had no idea this was a bad idea, and it seemed simpler than adding an intrinsic (though not by much) but today we know better.
It's strange that an ancient feature that is almost practically deprecated serves as one of your favourite examples.
I'm not an expert, but volatile was originally meant to access hardware registers and I believe it did fulfill its purpose at the time. I've never reasearched the official definition of volatile, but it is widely acknowledged that there are few or even no legit uses of it today, because it is not suited to general concurrent programming. This seems like a good SO answer: https://stackoverflow.com/questions/2484980/why-is-volatile-...
If I understand right, I think you could do multithreaded programming if you restrict to doing message passing of message structures that are marked all volatile. But you can't use much else, because non-volatile data accesses can be reordered around volatile data accesses.
In short, probably just don't use volatile. Use the "modern" memory barriers, those exist in C/C++ just as they exist in Rust.
> Would it mean anything to, for example, integer divide an MMIO fetch by fifteen and write it back? a/= 15; No. So then why make it a type qualifier?
This seems a strange example to me as well. This is equivalent to (a = a / 15). I.e. a volatile read followed by a volatile write. Not defending volatile at all, but I can't see what is so wrong here.
On a different note, attaching too many semantics to types is basically the complaint that I have about most languages. Types are too static and too inexpressive to capture all but the most trivial invariants, so complicated types often require messy and theoretically unnecessary workarounds. I always say, I'd almost like to types capture only the physical shape (sizes, alignment...) of the data, because that is extremely important for optimization and also it doesn't really change. In C, that is almost true, with some exceptions (signed/unsigned, and yes, the crufty volatile)
Addendum: Think about volatile like you think about const. If you use it to declare memory that is in fact different, it totally makes sense. If you do other weird things, it breaks (just like const).
This is unlike signed/unsigned storage specifiers which are not really attributes of the memory, and which create some serious complexities that lead to weird situations. But for practical reasons there haven't been any successful attempts to get rid of them.
Again, not defending volatile at all. Just saying that it probably made sense at the time at that it is not as ill-conceived as you imply. Volatile is cruft, but certainly does not disqualify the language. Just use modern synchronization primitives instead, like any other reasonable person.
One of my favourite examples is volatile. Volatile is more crazy in C++ but it's pretty crazy even in C. In both cases the standard basically shrugs, "Hope you know what you're doing because we sure don't" and offers no real insight into what this feature promises to do for you. But there is no other mechanism provided for MMIO.
Think of Rust's std::ptr::read_volatile (and write_volatile). These intrinsics do the thing you actually wanted (reading, or writing, a fixed size blob of "memory" that presumably wasn't really just RAM) and thus are important for writing device drivers and so on with MMIO.
[ You may be thinking, "But I need the correct size of blob read or written or my driver won't work", Rust has generics, so these functions are generic over the integer type you're reading/ writing, if you read a u64 that's a 64-bit read, if you write four u8s that 4 x 8-bit writes and so on ]
But C's volatile is a type qualifier instead. Why? Would it mean anything to, for example, integer divide an MMIO fetch by fifteen and write it back? a/= 15; No. So then why make it a type qualifier? When volatile was added to C they'd only just invented simple optimisations like re-ordering so they had no idea this was a bad idea, and it seemed simpler than adding an intrinsic (though not by much) but today we know better.