Just off my head, somethings I like about embedded rust
1. Memory (and by extension memory mapped peripheral) safety. In baremetal C on systems without memory protection unit, there's nothing preventing you from dropping a HAL_GPIO_WritePin(port, pin) anywhere in your code, or changing a peripheral setting register anywhere in the code. There's no warning from the compiler nor any checks against this. This means if you have multiple processes (esp. when developed by multiple developers), you can't trust that the state of the memory (and memory mapped peripheral) is in the state you left it in, someone / some process could have changed it under the rug.
In Rust, unless you specifically wrap your code in an
unsafe block, the compiler simply does not allow you to do that. Every bit of memory (and by extension memory mapped peripheral) has an owner, and only the owner of that bit of memory has write access to that bit of memory. Take for example a gpio pin, in Rust, the init code would take ownership of all the peripherals, break up the peripherals into individual objects (some SPI busses, some GPIO pins, some I2C busses etc), and pass the ownership these objects to various processes that now owns that bit of peripheral and has write access to it. The implication is your process can always trust that it has exclusive ownership of its memory objects, and can have confidence no other processes has altered its state. This is all done in COMPILE TIME, so there's no runtime overhead for having memory safety, even without a MPU.
This alone is very significant imo.
2. Modern tooling. In Rust there's Cargo, a build system + package manager that makes code building and reusing much nicer than C. If I want to add a library to my code, I just have to specify the name and version of the library I wish to use, and Cargo will integrate the library into my code properly and take care of all the dependencies and dependencies of dependencies. This is way better than C. Cross compiling is also a breeze, as Rustc uses LLVM, cross compiling means I just have to specify the architecture of the target and stuff just works. I don't need to maintain a version of GCC for linux, another version for windows, then arm-none-eabi-gcc for my cortex M stuff.
3. I don't usually allocate heap when programming cortex M, but when I do, it is so much safer in Rust. The compiler makes sure there's no memory leaks, no double freeing, no dangling pointers etc. all at compile time with minimal runtime overhead. This also means no garbage collector is needed.
4. Very strong, static, type system. Say you have a library function that implements sleep/delay. In Rust, properly written libraries will take a parameter of type time, instead of say a u32. In C, the programmer has to be cognizant of the physical meaning and unit of the number you pass to the sleep function. e.g. In stm32cube, HAL_Delay(x) takes a u32 and sleeps for x milliseconds. The programmer takes responsibility of making sure the number specified means milliseconds, not seconds, not microseconds, not ticks, not processor cycles. In Rust, the sleep function would take a generic parameter of time, and time can be a variable of type ms, or type us, or type second, or type ticks, and the sleep function would sleep according to the numeric value and type of the variable. So in Rust, your sleep function would look something like sleep(10_u32.millis()), or sleep(10000_u32.micros()), explicitly stating both the value and type, i.e.unit of the value. The compiler takes care of the part that translates the generic time type to the native type the sleep functions uses to count elapsed time, so no overhead again.
5. General attitude and mindset. In Rust, the programmer is not to be trusted, and the compiler does what it can to force you not to write certain types of bugs (e.g. memory leaks, out of bound array indexing), unless you explicitly wrap your code with
unsafe. In C, the programmer is assumed to know its sh*t and trusted to do whatever he/she wishes to do. This different attitude takes some getting used to, and rubs some people the wrong way. Many of us would like to think we are super smart and never writes bugs, but coding in Rust means you have to admit that you do write stupid and buggy code sometimes, and for certain types of coding errors, the compiler is capable of knowing better than you on what's buggy and what's not. This is particularly valuable in two situations imo,
- You work solo with no one to review your code
- You oversee a group of programmers with different levels of aptitude.
In these situations, your code review process is much simpler and the resulting code is much less error prone, because
- Illegal memory (and by extension memory mapped peripheral) access is prohibited by the compiler (point 1, 3)
e.g.
https://www.zdnet.com/article/microsoft-70-percent-of-all-security-bugs-are-memory-safety-issues/- Strong type system reduces the chances of passing the wrong type of data to the wrong function (point 4)
e.g.
https://en.wikipedia.org/wiki/Mars_Climate_Orbiter- You can focus your review effort to the handful of lines of code marked
unsafe, instead of scrutinising every single line of code.
This is longer than I thought it would be, but I think it is good food for thought. I welcome counter arguments, but please don't turn this into a holy war between different languages.