What is the current (early 2023) status of Rust for embedded programming

We will soon start a new embedded project (target arch: esp32 & arm contex-m, target os: FreeRTOS) and are having the luxury to select the programming language to use. We would love to use Rust as it would be much easier to write code correctly compared to C/C++. We looked into Rust 2 years ago and have gave it a try then but didn't feel it was ready then (especially in terms of IDE support, C interop, and easiness of low-level memory operations).

Could somebody give some insights on the current status of Rust for embedded programming is and whether it is advisable to pick Rust in a real project.

What, specifically, do you mean by "C interop not being ready"? The ready-ness (and user-friendliness, safety, etc.) of C FFI is bounded by how "ready", user-friendly, and safe C is. There's not much Rust can do about it, and I don't think there have been great many things that changed around C interop in Rust – simply because there's not much room to grow; C is C, and interoperating with it is not going to be particularly beautiful, ever.

This needs some elaboration, too. Rust gives you low-level control over memory operations; you can use raw pointers and the types and routines in the std::mem and std::ptr modules for doing the same kind of things in unsafe Rust that you would do in C. (However, the big selling point of Rust is abstracting these away. Even on an embedded platform, you shouldn't be littering your code with unsafety – otherwise, there's no much reason for using Rust.)

I feel that these two are the lesser issues with Rust anyway. The bigger issues is working cross-compilation (including core/std), support for Harvard architectures (if the platform is such, but I don't think Cortex-M is), hardware-level debugging, and the existence of good-quality (as in safely-abstracting) 3rd-party HALs. The Rust-on-ARM story is now pretty solid from what I can tell, so all in all I'd definitely give it a go.

Sorry for not being specific and clear enough. I didn't mean to criticize at any degree. 2 years ago when we tried to port part of an esp32 project from C++ to Rust as an experiment, our overall experience was that Rust for embedded was more experimental than production-ready, for the many "glitches" we were getting regarding IDE support, C interop, low-level memory operations, etc.

With enough efforts and good knowledge of Rust's internal working, we could probably have adopted Rust even 2 years ago. I created this thread hoping to hear about how the community feel about the "production-readiness" as a product marketed to non-rust-expert embedded programmers (i.e. take into consideration of how easy things could be done without resorting to advanced hacks).

The Rust-on-ARM story is now pretty solid from what I can tell, so all in all I'd definitely give it a go.

This is the type of answers I was hoping for. We will definitely give it a try if you are saying so.

To elaborate what you have asked:

What, specifically, do you mean by "C interop not being ready"?

I remember we ran into at least the following difficulties, most of which were workable but many of them hinder productivity:

  • the lack of "official" [bit fields] support (GitHub - dzamlo/rust-bitfield: This crate provides macros to generate bitfield-like struct. could probably be used instead)
  • we need to manually write converters for tagged union types and rust's enum types (though probably there won't exist any systematic solution to this..)
  • having to wrap board/OS APIs that is provided as C macros in C functions first (though this is probably rather a library issue)
  • special macros (e.g. the esp-idf's ESP_INTR_FLAG_IRAM macro which specify code placement and is required for interrupt handlers) understandably cannot be used in Rust. We ended up spending a lot of time on this and may/may not have got it to work by hacking linker scripts

easiness of low-level memory operations

It is conventional and sometimes required to write to specific memory addresses (most of the time memory mapped device/io registers) in specific orders where the memory addresses are provided as predefined macros in header files provided by the hardware vendors.

These are probably minor problems since there are usually (sometime dirty) quick hacks to get around but still make the whole experience less palatable. Good HALs definitely would help here.

Also you cannot do something like the below easily:

int port_id = 1;
SOME_MEM_PAGE[SOME_REGISTER_CLASS + port_id*SOME_REG_SIZE] |= 0xff & SOME_MASK

Although it is fair to argue that a function should be created for these kind of accesses, nevertheless it is still a demonstration of the opposite of easiness for doing low-level memory operations.

I was in a similar situation to yours 5 years ago, and came to the conclusion to not use Rust at the time. Mainly because I found Rust daunting.

Nowadays, I would definitely start a new embedded project with Rust (and I have), mainly because I feel more comfortable with the language. There are many excellent projects that make debugging easy (like knurling), integration with VSCode is great through rust-analyzer, there are many embedded multitasking tools (I like rtic a lot, haven't use any embedded OS).

Nevertheless, I assume from your post that you are not so experienced with Rust. If that is indeed the case, just be ready for a bumpy ride. Adjust your team (or manager) expectations: many of the things that in C are rather trivial (but unsafe), in Rust may no be as straightforward to do it safely. That may slow you down at the beginning, but it will pay off after a while.

1 Like