Topics:

  • Differences from C
  • Issues
  • Testing

Differences from C

One of the first things anyone going from C or CPP for embedded work to Rust will notice is the crazy verbosity of the syntax.

The syntax is the cost of being able to make so many of the memory safety claims that the language does. It’s both the worst and best part of the language as far as I can tell.

Writting Code:

Writing out Rust code isn’t too bad. The language has a lot of interesting features like iterators, result enums and the barrow checker.

Although the compiler will often complain about things, the messages are often useful and easy to understand.

Things like macros in rust take a bit more work to use and somehow manage to be even less understandable than the pre-processor hackery that you will see in C codebases.

One thing you’ll probably notice however during writing rust code is the growing desire to never refactor the codebase. This is one of imho the failings baked into the language. The explicitness and verbosity of the language makes simple changes hard compared to most other languages.

In C I might employ a vla(variable length array) as a parameter into something; however in rust you’ll have to either use a fixed length array, an external crate that offers that kind of functionality or start using the heap/stack crates for embedded use.

Reading Code:

Trying to read Rust code is where things go a bit sour. It suffers from a lot of the same issues you’ll find plaguing many OOP languages like cpp; except this time we call them traits.

Traits are good in concept and I can at least appreciate how they can assist in making the language safer, in the same way static typing does compared to dynamically typed languages like python.

Finding where those traits are defined and trying to figure out why the generated documentation doesn’t mention them is it’s own adventure. Maybe it’s actually a sealed trait, maybe the maintainer just didn’t feel like explaining it who knows.

Reading code from a Rust project makes using your web browser a necessity in order to figure out the functions available. Instead of being able to read either a system header file or one located in your project directory like in C you have to often search crates.io in order to find the docs.

It’s somehow taken the worst parts of javascripts NPM system and then made it part of a systems programming language. It’s Pythons version dependency hell on steroids because now it has to be compiled as well.

Pros:

  • more focused on safety.
  • Built in build system.
  • Built in test-harness.

Cons:

  • no support for bit-fields.
  • Single build system’s complexity.
  • overly verbose for many operations.
  • Most the useful features are part of std.
  • hard to interface with existing code(C or CPP).
  • huge binary size even with all the flags you can toss at it.
  • Slow compile time (slows down dev work).

Issues

Testing

Testing is….not fun in rust.

That’s probably the nicest way I can put this. All the features that make Rust great for memory safety also make it absolutely terrible for actually developing or prototyping a system over time.

I’ve heard someone else say that Rust is what you use when the design is already known and I can’t agree more.

To give some concrete examples lets pretend I’m trying to build out a crate that will handle some I/O operations from a CAN bus transceiver. Now I have an idea on what some of the broad details are but none of the fine points nailed down quite yet.

In C, I could start by writing some test code, stubbing out dependencies and then iterate through my normal TDD cycle until I had a nice modular piece of code written.

Not really the case in Rust. Rust wants you to define everything all at once otherwise it will refuse to work for the most part. This in turn means that you will end up spending most your time rewriting or copying the same setup and tear down code across tests.

Many of the nice tricks and pointer shenanigans you can pull of with C that make deep testing of code easy are also things that make it hard for a compiler to offer memory safety along with.

Want to do some bit shifting to reassemble a bunch of bytes into a u32 from a CAN frame? Ha, enjoy being forced to explicitly typecast all over the place with more parenthesis than should exist on a single line.

And yes I’ve used .into(), .from() and even written those trait implementations where needed for enums from time to time; but they only seem to contribute to the unreadability of the resulting code.

Conclusion

Do I like a lot of the concepts and features of Rust? Yes. Is it a great C language replacement for embedded use? No.

I think it’s a good cpp replacement as others have already said, but it’s a pain to prototype designs with and frankly a bit hard to do tdd with.

There are ways to work around those issues, but usually it’s at the cost of a increased binary size, more strain on the programmers mental model, and slower compiles times.

Rust makes it easy to get stuck with a large codebase with terrible design that can end up taking more work to refactor than it is to rewrite. It forces the developer to spend more time thinking about rust than the problems they are trying to solve with the language itself.

If you want to turn a 2 week embedded C project into a 6 month Rust project involving trying to write your own HAL for new hardware while also suffering the continually evolving language features with no standard ABI it’s the way to go.