> Isn't the order of operations important when talking to I/O?
Sometimes, yes. It’s hard to tell if this is one of those times without a closer look at the documentation or at least some experimentation, but because “Rust is Memory Safe(tm)” I think it is safe assume this is fine simply because it compiled, and without knowing anything about you, would recommend you assume the same, because, well, Rust. Obviously.
This is writing to the register space of some peripheral so no, the compiler knows nothing about that. It is also extremely common for such peripherals to have requirements on the order of writes and otherwise silently discard them. They also have all kinds of side effects and don't behave like normal R/W memory at all (e.g. clearing an interrupt is usually done writing a 1 to a register which will read 0 after).
The compiler doesn't know what's going on here, but fortunately the programmer does and they can use Rust's type safety to reduce the opportunity for foot guns.
Notice how the C code calls readb() with some address it got out of another structure adding to a constant from this file, whereas the Rust has readb() as a function implemented on the structure defining what it can write to. As a result, C code can easily mistakenly readb() something it didn't intend to, while the same mistake in Rust fails to compile.
Specifically, when you call that Rust readb() function with a constant like GPIO_SIZE that's not actually a GPIO register, it compares the constant you asked to readb to the size of the GPIO's I/O memory (1 page = 4096 bytes) and check it is smaller - if not the type match fails and your compiler tells you this won't work. No runtime crash, it doesn't compile.
If you don't have a constant, you can call a try_readb() function, this time you won't fail to compile if you screw up, but at runtime you get EINVAL instead of crashing.
> fortunately the programmer does and they can use Rust's type safety to reduce the opportunity for foot guns.
And what, they just didn’t?
This is an ordering bug. You could encode that into types perhaps, but I do not think that would be easy or as valuable as other ways to spend time.
> Specifically, when you call that Rust readb() function with a constant like GPIO_SIZE that's not actually a GPIO register, it compares the constant you asked to readb to the size of the GPIO's I/O memory (1 page = 4096 bytes) and check it is smaller - if not the type match fails and your compiler tells you this won't work. No runtime crash, it doesn't compile.
I think nearly every c compiler has had the ability to warn on something like this for thirty years. What’s the point? The rust programmer could do things differently but so could the C programmer?
It turns out that it isn't. I discussed in another part of the thread that these registers control interrupt handling between them. Maybe if you fiddle with them while something is bouncing the relevant IO pins up and down, you get a spurious interrupt, but I'm actually not sure which ordering would be least likely to cause that, the correct thing is almost certainly to just not blow up if you eat a spurious interrupt while fiddling with these registers or tell the device you don't want interrupts while fiddling with them, both of which are the same in either driver.
If you want that kind of guarantee in a Rust program, you can encode it into the types. It does make the implementation more complex, however, so it is not always worth doing.
Sometimes, yes. It’s hard to tell if this is one of those times without a closer look at the documentation or at least some experimentation, but because “Rust is Memory Safe(tm)” I think it is safe assume this is fine simply because it compiled, and without knowing anything about you, would recommend you assume the same, because, well, Rust. Obviously.