Regular readers will have noticed that I’ve spent the best part of the year trying to get Rust to work in diverse software scenarios. The common denominator was – in all cases – the absence of Rust’s embedded HAL. The talk I gave at last year’s ESE congress probably still holds true: The embedded HAL does not play nice with vendor provided tools. I’d go as far as saying: Using the embedded HAL actively harms your ability to use those tools. Further: The embedded HAL itself is hopefully underdeveloped. Take – for example – the serial communications part of the embedded HAL as seen here: https://docs.rs/embedded-hal/latest/embedded_hal/serial/index.html
This is all the embedded HAL has to say about serial communication. You get – I kid you not – two traits with a total of three methods. In my eyes, that is borderline useless, as it skips over all the complexities serial communication contains. How bad that is, is obvious if we take a look at the stm32f4xx implementation of the embedded HAL here:
https://docs.rs/stm32f4xx-hal/latest/stm32f4xx_hal/serial/index.html
This adds a whole bunch of additional traits to make serial communication somewhat usable (and also breaks the idea of an embedded HAL by doing so). However it also adds a bunch of – in my eyes – unnecessary complexity, as now I suddenly have to deal with clocks and manually configure my pins all the while still not fully capturing things like:
- DMA
- Line Idle
- Interrupt Driven Drivers
I don’t say the above is not possible, far from it, but you’lll have to – at least in parts – implement it yourself. And what you end up with will be something that – while being maybe more “idiomatic Rust” – will likely:
- Be less complete
- Contain more bugs
than what you would’ve gotten, had you just used ST’s HAL & the code generated by CubeMX.
… and it goes on
When working with TI’S MSPM0 recently I – as usual – tried to run Rust on it. Since I’ve integrated Rust in a variety of environments already, that was obviously not much of a hassle. I was mostly interested in the “friendlyness” of TI’s SDK towards Rust (i.e. how much effort was required to get bindgen to produce a working module). As it turned out: It took all of 20 minutes to have a working and easily extensible configuration and then I had a solution that allowed me to:
- Use SysConfig to configure my clocks & peripherals
- Use the existing TI drivers
With TI’s chip I did not have much of a choice, as there’s no HAL implementation available. This experience however really opened my eyes: The ease with which I was able to use Rust code on a that chip was somewhat liberating for my thinking on Rust in the embedded space. With all the stuff I already did this year I never really thought about it, but: Until then I – subconsciously – always thougth I’d need the embedded HAL and a matching implementation for any chip I’d want to run Rust on, if my Rust code would want to interface with hardware peripherals. This is obviously not true at all (and I probably should’ve noticed earlier). The main problem is, that Rust’s embedded working group directs us to the embedded book here: https://docs.rust-embedded.org/book/ which never mentions C interop and solely focuses on a Rust only approach and leads the reader to believe that this is *the* way of doing embdedded Rust.
Embedded WG – considered harmful?
I really do not want to talk too harshly about the work of the members of the embedded working group. For one they are all volunteers, and from what I’ve seen of them, they seem to be very dedicated and very capable individuals. With that said, I think the efforts here are going into the wrong direction if we want to have wider adoption of Rust. Further, the currently available documentation material is – at least somewhat – harmful to that purpose, as is the state of the embedded HAL. For people new to the community it very much seems like Rust outside your usual nRF/ST/RP2040 setup is no possible or only possible with a lot of effort.
Looking forward
Instead of spending time with creating solutions that are pure Rust (e.g. the m4rt crate – still a commendable effort) we should rather direct our limited resources towards supporting common external chips and providing drivers for those as well as robust support for common RTOS (e.g. FreeRTOS – https://github.com/lobaro/FreeRTOS-rust looks great but that should be an officiallish create IMO). This is actually a place where the embedded HAL could somewhat shine, even in its current form. But for most other things, that have to do with starting and configuring a device we should embrace C. We have to be realistic: While Rust has been gaining traction in the past years (and luckily the pace seems to be accelerating!), the chance, that chip vendors move away from C and provide Rust based tools is not very high. C is too entrenched in the embedded industry (at least heare in Germany – YMMV), with a significant number of companies still arguing about wether the risk of adding C++ to their codebases is worth it (see my post from earlier this year here: http://blackforrest-embedded.de/2023/05/04/rust-in-embedded-should-we-try-this/). Luckily Rusts capabilities for C interop are great and there’s no real reason not to use that, on the contrary: Embracing vendor tools will make the lives of embedded developers easier while allowing the community to spend time with projects that forward Rusts adoption. Otherwise Rust will have to play catch-up with chip vendors for the forseeable future and I’m not sure this is worth the effort.
As a Rust enthusiast and someone who is quite a novice yet interested in Embedded-Rust, I would like to hear about any standardization effort that may be in the making. While Rust / C interop is relatively easier (I have reasonably long background in Unix/Linux/C programming), the Vendor-specific C API must be be well-documented for a Rust programmer to use those correctly.
I have come here from your post on LinkedIn, and will look forward to hear more from you.
Vendor APIs are the way they are – some are well documented, some not so much. I’d assume any programmer who is able to get an embedded device to work at all is able to use a C API and will not be scared by the interop part.
I tend to agree with you. The Rust embedded-hal is laughably simplistic. It would be much faster and easier to use C-interop to get Rust working on microcontrollers.
However, what you lose are the guarantees that Rust gives you, when you cross that C-interop barrier. Once you’re in TI’s DriverLib, all bets are off with regards to memory safety etc.
I think _that_ is the Embedded-WG’s goal. They want Rust’s memory and type safety all the way down to the register level, which you’re not going to get with C-interop.
Unfortunately, the embedded industry itself needs to take Rust seriously and provide their own Rust HALs for their devices for this approach to work, IMO.
True, you lose the safety guarantees. Then again I’m not sure if having memory safety down to register level is even desireable (and I honestly have not thought through this and would not dare to try and qualify wether there are many gains to be had in real world applications). Given the current price we have to pay for it (i.e. not being able to use Rust in a lot of scenarios at all) I tend towards thinking, that it is not desireable. Apart from that you can get an astonishing level of safety even on top of C. You’ll have to roll your own flavor of a HAL (or, at the very least, a safe abstraction of the C API) obviously, but you’ll be in Rustland for most of the time and can enjoy all of Rust’s benefits.
And concerning the industry – you’re sadly right. As long as engineers still consider C for new projects not much is going to change. And this is – at least in Germany – still very much the status quo. There’s some movement judging by the number of attendees I had for my recent talks (mostly in the 300 – 600 people range), but I fear a lot of that is still just curiosity about the language itself rather than genuine interest in using it.