15 votes

The coming firmware revolution

17 comments

  1. [12]
    streblo
    Link
    One area that I can see being hard for Rust to break into is replacing embedded C. I work in an embedded shop with a bunch of microcontroller products running bare metal or some flavour of RTOS as...

    One area that I can see being hard for Rust to break into is replacing embedded C.

    I work in an embedded shop with a bunch of microcontroller products running bare metal or some flavour of RTOS as well as a few higher-end embedded Linux products.

    I would say around half of our team that works on the microcontroller products are former electrical engineers who have transitioned to writing embedded C. From my experience elsewhere, I don't think this is atypical. They usually don't have a ton of experience with software engineering and to them C is both (deceptively) simple and maps effectively onto the hardware they're working on. It's also familiar -- engineering schools here teach students in C rather than java or python so it's not something they new they need to learn. The subset of C they're working in -- no heap allocations and linted to the MISRA C standard -- already eliminates large classes of bugs rust addresses. I don't know if the large jump in language complexity and features is going to be an added benefit for them.

    I haven't written a ton of rust, just a couple of hobby projects, but I do like the language. I think in the long run it might eat C++'s lunch. But at times when I'm reading through rust projects I'm constantly spending more time deciphering what they're doing with the language itself rather than the system they are designing -- something I've encountered with some C++ projects but not something you really have to endure in a true low level language like C. Maybe something like Zig will end up being a better embedded C replacement than Rust?

    5 votes
    1. [8]
      mtset
      Link Parent
      I think this is largely exactly what Bryan is talking about - replacing firmware written in C or even assembly language with Rust. That said, nobody expects every firm out there to drop all their...

      One area that I can see being hard for Rust to break into is replacing embedded C.

      I think this is largely exactly what Bryan is talking about - replacing firmware written in C or even assembly language with Rust. That said, nobody expects every firm out there to drop all their C code today; part of "replacing C" is getting into curricula, training courses, and documentation.

      Overall I do expect Zig to become an important player in the space, but runtime-free memory safety is too good to pass up in too many applications for Rust not to remain a serious contender - unless someone comes along with a great, memory safe language that outdoes them both!

      4 votes
      1. [3]
        FlippantGod
        Link Parent
        I honestly don't know if Rust's extra guarantees matter so much in this space. It seems to me that "better than c" and "easy interop" and "small" are IMO altogether sufficient, and Zig can...

        I honestly don't know if Rust's extra guarantees matter so much in this space. It seems to me that "better than c" and "easy interop" and "small" are IMO altogether sufficient, and Zig can deliver.

        Making sure there aren't memory leaks is probably more important than hard guarantees of memory safety, and I'd argue that zig is an easier language to reason about the code than rust, which could lead to swifter adoption. The documentation and breaking changes will prevent it from gaining any real traction though, while rust is ready for production today.

        4 votes
        1. [2]
          mtset
          Link Parent
          This is a pretty common opinion. My experience of Rust leads me to disagree, but I think we'll all be better off whichever way things turn out - so I'm excited to see what happens!

          This is a pretty common opinion. My experience of Rust leads me to disagree, but I think we'll all be better off whichever way things turn out - so I'm excited to see what happens!

          4 votes
          1. FlippantGod
            Link Parent
            Definitely true! Super excited for either/both of these languages to thrive in the embedded space, and not micro python or whatever.

            Definitely true! Super excited for either/both of these languages to thrive in the embedded space, and not micro python or whatever.

            2 votes
      2. [4]
        streblo
        Link Parent
        I think one of the biggest problems Rust is going to face re: the embedded space is composability. C is already a very simple language. C typically written in the embedded world is an even smaller...

        That said, nobody expects every firm out there to drop all their C code today; part of "replacing C" is getting into curricula, training courses, and documentation.

        I think one of the biggest problems Rust is going to face re: the embedded space is composability. C is already a very simple language. C typically written in the embedded world is an even smaller and simpler subset of that. For people who are more electronics engineers than software engineers, this is a big selling point. There are a lot of these kind of people who work in the embedded world.

        I'm not saying it won't happen, but if it doesn't, this would be my best guess as to why.

        2 votes
        1. [3]
          mtset
          Link Parent
          Bryan goes pretty in-depth into this in the talk - I'm curious whether you disagree with what he says?

          composability

          Bryan goes pretty in-depth into this in the talk - I'm curious whether you disagree with what he says?

          2 votes
          1. [2]
            streblo
            Link Parent
            I don't disagree with anything he says, but I think we're talking about different things. He mentions no_std and the crate ecosystem -- that's certainly something that will pull people towards...

            I don't disagree with anything he says, but I think we're talking about different things.

            He mentions no_std and the crate ecosystem -- that's certainly something that will pull people towards Rust although (correct me if I'm wrong) it sounds like no_std is kind of what Zig will be by default -- pass in your own allocators so you can use this b-tree implementation in your embedded product.

            I'm saying all that doesn't matter if these embedded guys are trying to audit these crates and it's full of macros and lifetime annotations and all of these esoteric parts of Rust that make less sense to someone without a background in software engineering. If Zig can end up offering the same level of ecosystem with a mental model much closer to C I think that will be a big advantage in the microcontroller world.

            3 votes
            1. mtset
              (edited )
              Link Parent
              I guess the core of my disagreement here is that I don't think these are hugely esoteric - they're just different. In C you have non-hygenic macros that operate over text; in Rust you have...

              macros and lifetime annotations and all of these esoteric parts of Rust

              I guess the core of my disagreement here is that I don't think these are hugely esoteric - they're just different. In C you have non-hygenic macros that operate over text; in Rust you have (mostly) hygenic macros that operate over the AST. Those are different, but the Rust version is actually easier to reason about in the vast majority of cases.

              Similarly, it's not like most C code doesn't have lifetime annotations; the only differences between this C structure and constructor:

              typedef struct Cursor {
                  // buffer being read over; passed in by caller
                  *char buffer;
                  size_t buflen;
                  // this cursor's position in the buffer
                  size_t cursor_position;
              } Cursor;
              
              // does not modify or deallocate buffer
              Cursor new_cursor(*char buffer, size_t buflen) {
                  //...
              }
              

              and this Rust structure:

              struct Cursor<'a> {
                  buffer: &'a [u8],
                  cursor_position: usize,
              }
              
              pub fn new_cursor<'a>(buffer: &'a [u8]) -> Cursor<'a> {
                  //...
              }
              

              is that nothing that takes a Cursor, even a &mut Cursor or an owned Cursor, has even the possibility of breaking the contract that the buffer isn't modified, deallocated, or otherwise messed with. Instead of lifetime constraints being suggestions in comments, or implied, they are written out in such a was a to be intelligible to the compiler.

              I'd be just as happy to see Zig become a big deal in the embedded spaces - in fact, I would be willing to bet that most of them will be; that neither one will "win". But I don't think you should dismiss Rust as too complex to compete, even on very constrained systems.

              2 votes
    2. [3]
      time
      Link Parent
      Based on the embedded developers I know and interact with, I can definitely agree that it's going to be an uphill battle to get them to change how they do just about anything. Using C++ features...

      One area that I can see being hard for Rust to break into is replacing embedded C.

      Based on the embedded developers I know and interact with, I can definitely agree that it's going to be an uphill battle to get them to change how they do just about anything. Using C++ features in embedded development is still frowned upon by most of the embedded developers I know despite widespread support, and almost no downsides provided you use an appropriate subset of C++ that is embedded-friendly (i.e., no major standard libraries, nothing that heavily makes use of the heap, etc.). While I personally love rust for embedded development, trying to convince the embedded world at large to move away from C is going to take a great deal of effort.

      The subset of C they're working in -- no heap allocations and linted to the MISRA C standard -- already eliminates large classes of bugs rust addresses. I don't know if the large jump in language complexity and features is going to be an added benefit for them.

      I've actually been getting very into embedded rust programming of late, and I find that while things like avoiding the heap are indeed helpful in avoiding bugs, the greatest benefit of embedded rust is to practically guarantee that race conditions and other memory issues are not possible at compile time, with no run-time overhead. In my past embedded C/C++ projects, debugging race conditions has been extremely difficult and time consuming, due to their intermittent, ephemeral nature. I'll gladly trade a bit of language complexity to avoid the possibility of these types of bugs being possible in the first place.

      I also feel, as someone coming from a programming background of mostly embedded C/C++ and getting into rust, that people with the embedded C mindset are uniquely suited to understanding the memory safety features of rust such as the borrow checker. Embedded C programmers are already intimately familiar with having to manage memory at a low level, and the rust compiler just enforces an explicit, rigid framework to allow this to happen. I work with a local hackerspace to teach people programming, and the ones who already know C have been most receptive to learning rust concepts, as opposed to those who are more familiar with languages like javascript and python, that abstract away low level memory management to a much greater extent.

      Another reason that I feel rust is well suited to embedded development is that one of its big selling points is its zero-cost abstraction model. This allows for syntax used in higher level, more abstracted languages, but it doesn't cost anything in terms of processing time or memory.

      A good example of this being an advantage for embedded programming is setting a pin as an output, and toggling it. In a typical embedded C hal, there would usually be a function that has to check if a pin is configured as an output before setting it at runtime. This costs a small bit of time every time the pin is toggled.

      With embedded rust's model, each pin exists as a single entity in a peripheral struct, so due to the borrow-checker and compiler guarantees, there can only ever be one reference to that pin in use at any given time. When you want to configure a generic pin as an output, the pin is returned as a new type that has implemented a trait with functions to allow you to toggle the pin on and off. Since the pin is a singleton, and it's been transformed into an output pin type, it is impossible for it to be configured as an input now, and therefore you no longer need to check if it's valid to toggle the pin at runtime.

      While it is possible to explicitly configure embedded C to work in the same manner, it would require you to explicitly code your own compile-time types and checks, instead of using the standard tools provided by a typical manufacturer's HAL libraries. With embedded rust, there's a generally an agreed upon standard way of doing things as outlined in the embedded rust book, so changing architectures is also made simpler, and still results in the same zero-cost abstraction model working on every architecture.

      I will readily admit that the embedded rust ecosystem is still a long way from being a ready replacement for C, especially on less-popular architectures. Rust's default use of LLVM instead of gcc for compiling binaries immediately precludes many architectures not supported by LLVM. A rust gcc backend which will allow support for every architecture gcc supports is in the works but it's still a fair ways off. Even once the gcc backend is implemented, it will still be a while before embedded-hal libraries can be implemented for architectures not currently supported by LLVM.

      As part of my current project linked above, I found that the rust hal for the chip I am using is far from complete, and I've been working alongside my project to help implement the hal as I go. Many other embedded ecosystems are in a similar state of not-yet-complete-or-stable. This is definitely a valid reason that commercial embedded shops would want to avoid rust at this time.

      Overall, I feel that rust offers significant advantages in the embedded space, as the memory safety problems rust is fundamentally trying to solve as a language are the same problems that embedded C developers have had to manually address up to this point. The embedded rust ecosystem is still in its infancy, but it is growing rapidly, and I feel that eventually it will be a popular alternative to embedded C development.

      Disclaimer: I haven't had time to watch the video yet, so this is all only in response to the comments. Sorry if the video already covers these ideas.

      4 votes
      1. [2]
        streblo
        Link Parent
        Wow, thanks for the detailed reply! Haha I feel your pain. Everything is very risk-averse. I can't stand working on our C projects at work because MISRA is such a miserable standard to work in....

        Wow, thanks for the detailed reply!

        Using C++ features in embedded development is still frowned upon by most of the embedded developers I know despite widespread support.

        Haha I feel your pain. Everything is very risk-averse. I can't stand working on our C projects at work because MISRA is such a miserable standard to work in. Want an early return to check for null pointers? Sorry can't do that. Want to call a non-pure function on the right-hand side of an if statement? Sorry can't do that either. You end up with very simple code but it gets smeared across 5x as many lines, which I understand is the point but like, geez.

        Rust getting adopted would put an end to a lot of that, which I'm definitely all for so I guess we'll see how it turns out.

        With embedded rust's model, each pin exists as a single entity in a peripheral struct, so due to the borrow-checker and compiler guarantees, there can only ever be one reference to that pin in use at any given time. When you want to configure a generic pin as an output, the pin is returned as a new type that has implemented a trait with functions to allow you to toggle the pin on and off. Since the pin is a singleton, and it's been transformed into an output pin type, it is impossible for it to be configured as an input now, and therefore you no longer need to check if it's valid to toggle the pin at runtime.

        That's pretty neat! I haven't worked directly with a HAL in a while, but for the C implementation if you're setting those pins at compile time anyways are you sure the compiler isn't just doing away with those runtime checks? Or does your HAL interface require you to set everything up at runtime?

        2 votes
        1. time
          Link Parent
          There's a very good chance that the compiler is smart enough to remove some checks, but I haven't really looked into it very thoroughly. It's been a while since I was working on something where...

          for the C implementation if you're setting those pins at compile time anyways are you sure the compiler isn't just doing away with those runtime checks? Or does your HAL interface require you to set everything up at runtime?

          There's a very good chance that the compiler is smart enough to remove some checks, but I haven't really looked into it very thoroughly. It's been a while since I was working on something where the timing of digital pin switching mattered, so my memory is a bit fuzzy. The last time I remember running quantifiable tests, I think it was on an Teensy 3.1 or 3.2 board (can't remember which), and using a library with set_high(pin_number) or set_low(pin_number) functions in some platformio library, it came out to be about 5 times slower than just manually setting the register bit. It has been a while, so I may be recalling things a bit off, but that was the gist of it.

          The main takeaway, for me at least, is that while it is possible to make things work the same way in C/C++, it's nice that the default way in rust's embedded-hal is also the way with the least overhead.

          3 votes
  2. [2]
    mtset
    Link
    A great, short talk from Bryan Cantrill, previously of Sun, Oracle, and Joyent, now of Oxide Computer Company, on the future of open source firmware written in Rust.

    A great, short talk from Bryan Cantrill, previously of Sun, Oracle, and Joyent, now of Oxide Computer Company, on the future of open source firmware written in Rust.

    3 votes
    1. dootdoot
      Link Parent
      Thank you for sharing this

      Thank you for sharing this

      2 votes
  3. [3]
    Moonchild
    Link
    Oh, hey, I remember openfirmware. That turned out great...

    Oh, hey, I remember openfirmware. That turned out great...

    1 vote
    1. mtset
      Link Parent
      Heh. I think part of the point is that Rust does a lot of things for firmware devs that Forth doesn't...

      Heh. I think part of the point is that Rust does a lot of things for firmware devs that Forth doesn't...

      2 votes
    2. FlippantGod
      Link Parent
      Well, it did a lot for modern firmware. Device trees and all sorts of system bringup implementation ideas stem from it.

      Well, it did a lot for modern firmware. Device trees and all sorts of system bringup implementation ideas stem from it.

      1 vote