I’m no rustacean, but I agree - for vastly different reasons, though.
A type having similar algebraic properties to a set of assembly instructions isn’t what being close to the metal is about. It isn’t a thought experiment or learning experience - it is the ability to interact directly with lower level parts of the system, including the kernel and the hardware. It’s a great power that comes with great responsibility - a power and responsibility that safe rust will never, ever grant you.
Unsafe rust is essentially the same as C++ in terms of level of control. It allows volatile memory access, for example, and it allows embedding asm blocks. It basically gives you the abilities of C with additional tooling and compile time power, which is why I compare it to C++.
Safe rust would be quite far away from the metal. It provides a safety net that prohibits you from doing anything metal-like. The lower you go, the more “trust me, bro” and “hold my beer” you’ll need to incorporate, and safe rust is designed specifically to void bro-trusting, beer-holding situations. A kernel or a driver written in safe rust will never be possible.
But that’s where my terminology falls apart. Rust isn’t a choice of safe and unsafe, it is and will always be a dance between the two.
> A type having similar algebraic properties to a set of assembly instructions isn’t what being close to the metal is about.
See if you can guess how those "similar [actually, identical] algebraic properties" are achieved when the Rust software is compiled to machine code. Keep in mind that Rust has a reputation for going real fast...
If you guessed that the answer is it produces exactly the same machine code, you're correct.
> Safe rust would be quite far away from the metal. It provides a safety net that prohibits you from doing anything metal-like.
Nope. This is a crucial misunderstanding, it supposes that Safe Rust is something like Java or even Python and it really isn't.
It's perfectly safe to implement wrapping integers using just the actual CPU's wrapping arithmetic instructions and so that's exactly what Rust does.
Very often the machine code emitted for a nice safe, expressive Rust program is identical to the code for the trickier to get right C program that looks more "bare metal" - if it copes with all the edge cases correctly. Example: C code that properly checks whether the thing it just got wasn't NULL, versus Rust code that writes if Some(thing) = ... thus pattern matching the "it's a thing" check. If you forget to check in the C code it faults (or worse) at runtime, same error in the Rust won't type check and doesn't compile. Once you've made both of them check the machine code when you compile them is identical, that if Some(thing) check will compile to the same conditional jump as the C not-NULL check.
Sometimes there's less machine code... for the Rust, because the compiler knows Rust isn't stupidly aliasing mutable variables, so it can avoid some spills that must be generated in C "just in case".
Close to the metal doesn’t mean “I can create an operation that I could do in assembly, and it compiles to that assembly”. It doesn’t necessarily mean “fast”, either. If that were the case all compiled and optimised native languages would count. We aren’t talking about integer addition here - we’re talking about interaction with hardware.
The general issue with safe rust in these circumstances is exactly what makes it safe in the first place - it wants to be firmly in the driving seat. Want to access memory? You can only do so if it is being “managed” by rust (an abuse of terms, really, as that management is done at compile time). But with device drivers, there’s a pretty good chance you’ll want to access memory at a certain fixed address, managed outside of your program, and there’s a pretty good chance that it is volatile.
Safe rust has already slammed on the brakes, exited the driver’s seat and is running in the rain just to get away from the thought of such things. Unsafe rust could easily manage it, otherwise embedded rust wouldn’t exist.
For MMIO loads and stores you want intrinsics which do an MMIO load / store, which is exactly what Rust provides. The fact this is an "address" in "memory" couldn't matter less, it's not really memory, that's just for the convenience of the CPU so that we don't need two extra instructions (and I believe ironically on some embedded CPUs we do have special instructions and we do need to use them for IO to get the desired behaviour, so Rust ends up making that case easier...)
What C does here instead is a type system hack named "volatile" types. Let's label the type so that we know it's not really memory, but then we'll allow all the same operations as for regular types even though that doesn't make much sense. Ideally programmers avoid using any of the resulting nonsense operations, in practice they mostly seem to just hope it magically works, after all the C looks like it should work...
A type having similar algebraic properties to a set of assembly instructions isn’t what being close to the metal is about. It isn’t a thought experiment or learning experience - it is the ability to interact directly with lower level parts of the system, including the kernel and the hardware. It’s a great power that comes with great responsibility - a power and responsibility that safe rust will never, ever grant you.
Unsafe rust is essentially the same as C++ in terms of level of control. It allows volatile memory access, for example, and it allows embedding asm blocks. It basically gives you the abilities of C with additional tooling and compile time power, which is why I compare it to C++.
Safe rust would be quite far away from the metal. It provides a safety net that prohibits you from doing anything metal-like. The lower you go, the more “trust me, bro” and “hold my beer” you’ll need to incorporate, and safe rust is designed specifically to void bro-trusting, beer-holding situations. A kernel or a driver written in safe rust will never be possible.
But that’s where my terminology falls apart. Rust isn’t a choice of safe and unsafe, it is and will always be a dance between the two.