Settings

Theme

Embedded Rust or C firmware? Lessons from an industrial microcontroller use case

arxiv.org

162 points by mrtz 2 months ago · 171 comments

Reader

dgacmu a month ago

Authors are from STMicro, polytechnic Turin, Freie universitat Berlin, and Inria. Examined writing firmware for an IOT sensor platform. From the abstract:

> Two teams concurrently developing the same functionality (one in C, one in Rust) are analyzed over a period of several months. A comparative analysis of their approaches, results, and iterative efforts is provided. The analysis and measurements on hardware indicate no strong reason to prefer C over Rust for microcontroller firmware on the basis of memory footprint or execution speed. Furthermore, Ariel OS is shown to provide an efficient and portable system runtime in Rust whose footprint is smaller than that of the state-of-the-art bare-metal C stack traditionally used in this context. It is concluded that Rust is a sound choice today for firmware development in this domain.

  • blub a month ago

    One of the authors commented below that the “teams” were actually persons and the Rust person was an intern.

    This is even less serious than the typical pattern of grabbing random students for experiments and then drawing conclusions about the general population.

    • torginus a month ago

      Not sure about your life experiences, but every new, from-scratch project I have undertaken has looked like 1-2 or at most 3-4 people on good terms who really pulled their weight, with the rest being basically not dead weight, but the management overhead they caused ate up most of the productivity they brought to the table.

      • blub a month ago

        Even if that were the case, working in a team is very different from working solo.

eggy a month ago

We passed on Rust for Ada/SPARK2014 to write to bare metal on Cortex-M processor for real-time, high-integrity, and verifiable mission-critical software. Rust is making strides to be a future competitor, but it's new to the formal verification tooling and lacks any real world legacy in our domain. Ada's latest spec. is 2022. Other than AdaCore's verified Rust compiler, Rust still does not have a stable language specification like C/C++, Lisp, or Ada, SPARK 2014. I have no doubt that it will start rising to tick all the boxes that Ada/SPARK do right now with their decades of legacy in high-intetrity, mission-critical applications. The mandate to use memory-safe software put into effect this past Jan 1 2026 puts some wind in Rust's sails, but it's more than memory-safety in this domain. Plus, I do not enjoy Rust, but Cargo is nice. We're looking at Lean for further assistance in verifying our work. I think there was and is lot of Rust evangelism that will also carry it forward and boost even more Rust popularity,

  • Filligree a month ago

    Presumably, if you use formal verification then that includes memory safety anyway? Would seem strange if it does not.

    • irishcoffee a month ago

      Formal verification requires a spec and a very large, very expensive amount of tooling to be developed.

      My understand is that both these things are in work, and that neither of these things exist yet.

      • eggy a month ago

        Yes, and AdaCore's tooling is formally verified and produces reports already familiar to aerospace, railway, and auto auditors for verifying certifications making it attractive to this industry segment of high-integrity apps. Memory safety is taken care of mainly through the features Ada/SPARK2014 offer in creating safe, high-integrity programs, correct.

        • irishcoffee a month ago

          Yeah right now it’s usually C, but if I had a choice I’d use Ada. I’ve never done a graphical interface with Ada, and I have with OpenGLSC using C.

          I’m sure at some point there will be an accepted formal verification toolchain for rust, I hope to never use it.

  • torginus a month ago

    I've read (from one from one of the people that contribute to Rust afair), that they were involved building formal verification for Rust, and found that the control flow is just so complex that its very hard to use the language like this.

  • IshKebab a month ago

    There's this: https://rust-lang.github.io/fls/

    But I think the lack of a formal specification is really not as big a deal as it's made out to be. It's one of those "think of a technical reason to justify a decision I've already made" excuses.

    Obviously it would be great if Rust does get a full formal specification but I think avoiding it because it doesn't is just silly. C++ has a formal specification... which frequently has bugs and ambiguities. They aren't magically right and either way you're going to need to do a lot of non-formal testing as well as formal verification if you want confidence in a design.

    This is true even for domains where formal verification is routine like SystemVerilog. I've seen designs pass formal but fail in simulation or vice versa due to subtle differences in the semantics. (Hopefully that can't happen for Rust but you get the point.)

  • throw848tjfj a month ago

    Rust is not really memory safe if you combine it with external libraries. Too many "unsafe" keywords, and lack of tooling for code analysis and verification.

    Edit: With c, you can do memory safety analysis on all system libraries and entire Linux kernel. Some OS kernels, libs and languages do not have dynamic memory allocation at all!

    Some languages are memory safe! Learn more about embedded programming!

    • estebank a month ago

      Under that rubric, no language is memory safe.

    • MarsIronPI a month ago

      To me Rust is just a nicer language than C. I don't care too much about how easy the language makes memory safety, provided it doesn't make it difficult. But Rust's type system, higher-order functions, polymorphism, macros, etc. make it more pleasant to write than C for complicated programs.

    • tormeh a month ago

      This is correct. Some widespread libraries leak memory, for example. I love Rust, but I don't think this happens much in Java land.

      • AlotOfReading a month ago

        Rust's definition of memory safety doesn't consider leaks unsafe, which isn't to say your system requirements can't .

the__alchemist a month ago

Good article! I will give you my 2c, as someone in this space mostly for hobbies, but with one active work project:

Rust is fantastic for embedded. There are no hard obstacles. The reason to do it IMO is not memory safety, but because holistically the language and tools are (to me) nicer. Enums, namespacing, no headers, `cargo run --release` "just works". (I have found, at least in OSS, compiling C embedded project is a mess. Linker errors, you need to have a certain OS with certain dependencies, there are many scripts etc). Good error messages, easy to structure your programs in a reliable way etc. Overall, I just find it to be a better designed language.

I have found the most fundamental tooling for rust on Espressif's RiscV, and Cortex-M ARM for various STM-32 variants to be great. The cortex-m crate, defmt, and probe-rs, and the PAC project is fantastic.

On the down side, I have have to build my own tooling. I wrote and maintain my own HAL for STM32, and have had to write my own libraries for every piece of hardware. This comes with the territory of a new language, and suspect this will gradually improve this time - especially with vendor support. Because the fundamental libraries are around, this is just reading datasheets and making rust functions/structs etc that do the MMIO as described in the datasheets. Can be tedious (Especially if building a complete library instead of implementing what you need for a given project), but is not an obstacle.

My most complicated rust embedded firmware was a FPV-style UAS. I did it without an RTOS, using interrupt-based control flow.

  • acstapleton a month ago

    As a professional in the space, this echoes my experiences. I’m far more productive in Rust than C, despite having many more years programming embedded software in C. Cargo and the crate ecosystem are a dream compared to the lack of any easy-to-use build tooling and the difficulty of integrating third party libraries in C.

    Furthermore, the code I’ve produced in Rust is generally as fast (or faster) than the code I’ve written in C for the same task, and it’s easier and faster to write.

    And I also maintain an open source HAL for an STM32 family! Previously those have just been in house HALs in C because there were no such community efforts.

    • the__alchemist a month ago

      Very cool! Which one? I have been using mostly G4 and H7 on my personal and work projects, but the HAL (`stm32-hal2` is the crate name) works for most of the one s in a certain time band; i.e. not any that were obsolte when I started it, and spotty or no support on some of the newer ones like U series and H5. And weaker / non-vetted support on MCU variants I haven't used, or haven't used a MCU/feature combo on.

      I think in the future if I do an embedded rust project on a new MCU where there isn't an existing HAL, or one that is more work to repair than start over, I would just implement the subset needed for a project's reqs. Easier to keep track of scope that way. Currently the challenge is "X periph on Y variant of Z STM-32 family doesn't work under A condition" or "Doesn't work after this PAC update changed the syntax".

      • acstapleton a month ago

        I’ve been developing https://github.com/stm32-rs/stm32h5xx-hal. It’s been a slow process adding functionality because I’ve focused on what we need from it, so it’s missing a lot of functionality. Making progress bit by bit though.

        • the__alchemist a month ago

          V cool! I think I got the RCC and basics working on that but I think my impl is missing and or broken for many periphs. Similar to H7 in a lot of ways though!

cmrdporcupine a month ago

I'm a big fan of Rust on embedded (and think embassy in particular is awesome, haven't tried this Ariel OS.)

I would say however that there's still toolchain issues here. There all kinds of MCUs that simply don't/won't have a viable compiler toolchain that would support Rust.

e.g. I recently came from a job where they built their own camera board around an older platform because it offered a compelling bundle of features (USB peripheral support and MIPI interface mainly). We were stuck with C/C++ as the toolchain there, as there was no reasonable way to make this work with Rust as it was a much older ARM ISA

bArray a month ago

> It is concluded that Rust is a sound choice today for firmware development in this domain.

This conclusion was reached with a single experiment.

> Two teams concurrently developing the same functionality — one in C, one in Rust — are analyzed over a period of several months.

> Furthermore, Ariel OS is shown to provide an efficient and portable system runtime in Rust whose footprint is smaller than that of the state-of-the-art bare-metal C stack traditionally used in this context.

> The authors thank Davide Aliprandi and Davide Sergi of the STAIoTCraft team, and the wider Ariel OS team.

So one team had Ariel OS developer support, and it's unclear what support the other team had. Seems fair.

In Figure 12, they simply stop optimizing the code once desired rate is reached. Just at the end of the project the Rust firmware gets over a third performance boost, most likely from their OS developers.

Additionally, there is a claim that "Ariel OS is shown to provide an efficient and portable system runtime" - but there are no real tests for portability are conducted. Worst still:

> Where C-based projects require a separate project setup and manual code copying per target, Rust on Ariel OS consolidates everything within a single project [..]

This claim is just not true. This sounds like somebody that is not as familiar with C.

  • kaspar030 a month ago

    > In Figure 12, they simply stop optimizing the code once desired rate is reached.

    Yes. The goal was to handle the maximum data rate of the used sensor, and stop there. Time was limited on both ends.

    > Just at the end of the project the Rust firmware gets over a third performance boost, most likely from their OS developers.

    The ST intern found those boosts all by himself. They compared the exact MCU & peripheral initialization of the C and Rust firmwares, tightened I2C timings (where STM Cube has vendor tuned & qualified values), and enabled the MCU's instruction cache, which somehow is not default in Embassy's HAL. We were quite impressed actually, the last days before the deadline were quite productive, optimization wise.

    • bArray a month ago

      > Yes. The goal was to handle the maximum data rate of the used sensor, and stop there. Time was limited on both ends.

      I understand, and I understand that there were limits to what could be done with the resources there were. What irks me is the strength of the claim made without enough evidence to make it.

      > The ST intern found those boosts all by himself. They compared the exact MCU & peripheral initialization of the C and Rust firmwares, tightened I2C timings (where STM Cube has vendor tuned & qualified values), and enabled the MCU's instruction cache, which somehow is not default in Embassy's HAL. We were quite impressed actually, the last days before the deadline were quite productive, optimization wise.

      Fair enough, hats off to the intern. This kind of thing is common in MCUs, even on low-end CPUs weird defaults can be selected. But the involvement and influence of the OS developers remains unclear.

      Again, there's just not enough data to make such strong claims. I think the paper could easily make recommendations, it could say that at least in some cases (as evidenced) Rust could be a reasonable choice, and it could make an argument for further work.

  • ambicapter a month ago

    > This conclusion was reached with a single experiment.

    No shit. This is the conclusion reached at the conclusion of this experiment. This part of your comment can be removed with no loss of clarity, I think.

    • bArray a month ago

      I think you miss my point. I don't think that this conclusion can be reached with the (singular) experiments performed because there is a lack of data to draw it.

      If I ran an experiment where I gave a cancer patient bread, and then they recovered from cancer, I couldn't then say: "It is concluded that <bread> is a sound choice today for <cancer treatment> in this domain.". You would rightfully jump up and down and demand further experiments to increase the confidence of the result before drawing the conclusion.

      It could have been concluded instead that there is a case for further experiments to be conducted, or that Rust could be approaching a maturity where it could be considered for some firmware projects. But as it stands, the conclusion is far too strong given the experiments performed.

fjfaase a month ago

Really strange the the C JSON parser has to use malloc where the RUST version does not. As if it is not possible to write a JSON parser in C that does use malloc. I presume that the syntax of the commands that the device will accept is known, and than there is no reason why you have to build a DOM of the JSON before you can process it. Apparently, the RUST version can do it. I really begin to question the abilities of two teams if the one team failed to implement a JSON parser solution without using memory allocations.

  • kaspar030 a month ago

    Part of the C protocol implementation is generated, and that generator chose the JSON parser. As it worked and there was plenty of memory left on the MCU, it was kept.

    We're mentioning this in the paper: "The heap is entirely attributable to Parson's dynamic allocation of JSON tree nodes; as memory usage minimization was not a key goal, we kept Parson (the JSON parser used by the PNPL code generator by default), noting that there are less memory heavy options that do not require a heap at all."

    • dundarious a month ago

      Wasn't memory one of the key indicators looked at?

      > The analysis and measurements on hardware indicate no strong reason to prefer C over Rust for microcontroller firmware on the basis of memory footprint or execution speed.

      I admit I have not carefully read the paper, and am collating info from comments here, so I may be fully mistaken. The word "strong" also allows for much interpretation, that I'm not a priori critical of, but am skeptical of.

  • megous a month ago

    Yeah, you can comfortably work with JSON in C directly on top of the string buffer containing it. Your representation for any JSON entity will just be const char pointer. It's possible to implement JSON path on top of this, and all kinds of niceties, and it's not slow.

    Megatools is an example of such a code https://xff.cz/megatools/ / https://xff.cz/git/megatools/tree/lib/sjson.c

kaspar030 a month ago

One of the author's here, if there are any questions!

  • Galanwe a month ago

    Isn't there a nasty selection/volunteer bias at play with the developers?

    • kaspar030 a month ago

      You mean with the "two teams" that were tasked to develop the C / Rust versions?

      Yeah of course. Then again - they were one person teams, where the C "team" had years of experience in stm32 / embedded C / stm32 cube development and churned out that handwritten state machine in just days. The Rust "team" was a pre-masters intern with only minimal embedded Rust experience. They ran into all the pitfalls with (async) embedded Rust, but corrected towards the end.

      • jacquesm a month ago

        That does not seem like even close to a fair comparison and makes me wonder how valid the conclusion is. Effectively this is two times n=1, if you use 'teams' when you actually mean 'individuals' then that's not really proper reporting.

        I do applaud you for having the same work done twice but it would have been far more meaningful to have two actual teams of seasoned developers do this sort of thing side-by-side. The biggest item on the checklist would be the number of undiscovered UB or UB related bugs in the C codebase and to compare that with the Rust codebase on 'defect escape rate' or some other meaningful metric.

        • pitched a month ago

          I think there’s another hidden issue of testing how new devs use the language vs. those seasoned devs. I expect someone with a few months of experience would prefer Rust (fewer footguns) but someone with more experience would prefer C (the sharper knife). The flavour of the thing changes as we age.

          • jacquesm a month ago

            The problem with C - and I'm saying this as a life-long C programmer and not exactly a fan of Rust - is that C is indeed very sharp but it will cut other people just as easily even though they are far downstream of the original programmer, as well as the users of those programs. And it is extremely hard to not accidentally fall for one of the many pitfalls of C.

            I've got my own set of restrictions for when I'm coding in C based on many nights spent poring over various pieces of code and trying to find a way to do it better and safer without outright switching languages. I do believe it is possible. But at the end of all that you have essentially redefined the language in a way that probably no other C programmer would like or agree with, and it would still require very good discipline.

            So having languages with fewer footguns is good, as long as the lack of one kind of footgun isn't replaced by a other kinds of footguns. It is one of the reasons I'm interested in the FIL-C project.

            https://fil-c.org/

            • pitched a month ago

              Fil-C says it doing runtime checks which is fantastic for debug builds (like valgrind) but I worry a bit about performance with that for release builds. Valgrind can be pretty rough!

              My personal view is that good C code looks a lot like Rust where ownership is clear and a borrow checker would approve. The mindset that Rust forces you into is the same one you should be using when writing C.

              The longer-term concern is that, if you’re spending late nights learning Rust, it’s probably with the borrow checker. Late nights with C, it’s probably with memory management. One of those two is a bit more applicable to understanding computing at a deeper level.

      • the__alchemist a month ago

        I hit those pitfalls with async and moved on. It's popular in open source rust embedded circles, but not my cup of tea.

  • edderly a month ago

    If memory is a concern why are you trying to send JSON to a memory limited device?

    • kaspar030 a month ago

      The used protocol was part of the requirements, so the existing web service could be re-used.

      • edderly a month ago

        Yeah, a common stupid requirement. Perhaps a selling point for any solution would be to deploy a common serialization/de-serialization package that can be used on both the cloud and end point side.

        • torginus a month ago

          Why? In IoT stuff, its very useful if you can talk to your devices via standard internet protocols, otherwise you have to introduce some pointless 'gateway' node for that.

          I mean sometimes efficiency matters a lot, but a lot of other times, interoperability is more important.

          Text based IO with microcontrollers over tty has been quite a standard thing even decades ago.

          • edderly a month ago

            Interoperability would mean you have a meaningful protocol encoded within JSON. JSON itself offers little value.

            • torginus a month ago

              From the paper:

              > The command-response protocol and binary data format are described in device models generated using DTDLv2 [10], a JSON-based language for describing digital twins. These models are used within the Azure IoT Plug and Play (PnP) framework [11], which STAIoTCraft adopts for datalogging

  • photochemsyn a month ago

    I read the paper looking for what kinds of static analysis, fuzzing, sanitizers, formal tools, HIL testing, binary analysis were used - didn’t see anything.

    I’d guess that’s an area where C tooling is pretty far ahead of Rust tooling at present?

  • MeteorMarc a month ago

    Nice to see serial comms supported. Are I2S and CAN on the roadmap? Do you see any sensor module suppliers support ArielOS?

    • kaspar030 a month ago

      1. So Ariel OS is based on Embassy - IIUC I2S and CAN has some support upstream. That can be used already, although not using Ariel's usually fully portable APIs.

      2. Well, ST has released official Rust drivers for a bunch of their sensors. They're built on embedded-hal(-async), so can directly be used with Ariel OS. There is probably more.

    • the__alchemist a month ago

      Note: I'm not using the same tooling, but CAN and I2S have worked well for years on STM-32/rust. You just need to interface with STM32's SAI (ditital audio peripheral) and CAN. There are high-quality portable libs for both the legacy "BX" CAN and FD-CAN, which will work on any STM-32 variant. The SAI will have to be HAL-specific, but I have used it on both G4 and H7 variants for PDM mic arrays.

  • Ygg2 a month ago

    Why Rust and not say Ada?

    • kaspar030 a month ago

      "Customers are asking for Rust" would probably be the reason why ST is looking into this.

  • phwak a month ago

    Unrelated, but it's "authors," not "author's."

    "Author's" is possessive; "authors" is plural.

  • nlarion a month ago

    What's the tl;dr, or intuition to gain here?

torginus a month ago

Hhaving worked on quite complex embedded projects, my 2 cents is that dynamic allocation should be avoided as much as possible (and its been possible to avoid it 100% of the time for me).

Memory layouts are often pre planned, with hand-written linker files, which are sometimes even necessary, as there are quirks like DMA only being able to access certain addresses etc.

In embedded, engineers often want hard real time guarantees, very high (essentially unfailing) reliablity, at the lowest possible price points.

Dynamic memory allocation is often no good - embedded allocators either waste RAM, have large code sizes, have worse runtimes than their desktop-grade cousins - something like jemalloc generates 10x as much code as the rest of your app, and assumes your heap is at least megabytes in size.

On the other hand, embedded-grade allocators often use algorithms that are prone to fragmentation, unpredictable runtimes, and tend to be less tested in general, while still wasting RAM and Flash.

Having an allocator that has a bad runtime behavior can basically ruin your hard runtime guarantees, and if you prealloc all you memory you will never run out - on the other hand, even the paper states they measured not calculated the max memory usage, meaning we have no knowledge of what the actual max is.

TheMagicHorsey a month ago

My biggest gripe with Rust, which certainly reflects my own shortcomings, is that when I go back and revisit simple Rust programs I wrote more than a year ago, it takes me a long time to understand what I was even doing in a particular part of the program. This is my weakness ... I'm not great with Rust and I don't use it enough to get better. But it is what it is.

In contrast, when I go back and read Go or C code I wrote years ago, I have no trouble at all quickly figuring out what I was doing in the small programs I write.

The way these issues manifest themselves, as it recently did, was I went back to add a simple addition to a CLI tool I wrote for myself a year ago, and I was having trouble doing it because I couldn't quickly understand what I had been doing a year ago ... so I just had an AI agent do it for me. This was the kind of change that if it was a Go program, I would've done manually myself in about 5 or 10 mins.

davemp a month ago

I'll start of by saying I really hate C (also love it), and welcome improvements; but I have a few criticisms:

- Sensor agent is such a rancid name for a remote sensor that I feel a need to public say so. Please don't use marketing names for things that already have more descriptive names.

- Rust uses a full RTOS and C uses the mediocre ST HAL (vendor specific). Immediately apples to oranges. Also I've never heard of the C JSON library and it looks sketchy at a glance so that will also hurt the comparison.

- Streaming slow sensor data with a 160MHz 786KB/2MB MCU is not a good test in the slightest. You could probably use something like micro python here and be done. No one is reaching for bare metal C here. Also no one serious about performance is using JSON serdes. If you're using bare metal C, you're likely trying to push the limits of your hardware or doing something so simple that you won't be tempted to reach for terrible third party libraries.

- Does the Rust code base use the 'unsafe' keyword anywhere, including the RTOS? If so, it's not memory safe without additional formal verification.

Overall I'd say this paper has approximately zero value wrt its stated goal of comparison.

serhack_ a month ago

off topic question: why is there no source attached to this paper?

cozzyd a month ago

Do linker scripts look the same in the rust tool chain? (E.g. for implementing a bootloader?)

  • QuiEgo a month ago

    Yes. It uses the same linker as a C/llvm toolchain, by the time you link the object files from Rust are in the same format as the object files from C would be. You use the exact same linker script format too.

ezekiel68 a month ago

Lots of handwaving about fear of rust changing too often and even MORE downvoting of this as if there is no concern whatever. Neither of these extremes are valid.

For me it comes down to the old standby: just vendor the .cargo build chain into your repo and be done with it. There you go. Lock in the year edition, the version, the features, the quirks, and the bugs. Just like game devs did with game engines or OpenGL or Direct X versions.

Keyboard Shortcuts

j
Next item
k
Previous item
o / Enter
Open selected item
?
Show this help
Esc
Close modal / clear selection