I've been looking for a better language than C/C++ for years for small embedded projects. Recently I found some time to finally look at Rust. Initially, I was very excited, it's a fresh take and there are a whole lot of things about it I like. I read a lot of the Rust Embedded book, ran into the headless crate description, and thought "These Rust guys really get it" WRT to codebases which largely allocate data statically. We have no heap in our application, for instance.
But having spent a bit of time actually to reimplement some of our code in Rust, it's not turning out to be so easy. Perhaps an example will illustrate things better. Our codebase - like so many small embedded codebases - consists of modules that are almost always singleton "Managers" of defined functionality, with largely private state data and narrow external interfaces. Threading is kept as simple as possible, data hiding is used, and data ownership is carefully defined. A sample overly simple "manager" might be defined like this:
static struct TableEntry { // static really means private
float value = 10;
int tickDelta = 0;
uint8_t flags = 0xf0;
};
class FooManager {
int state1 = 3; // actually some enum
float value1 = 3.14;
uint8_t flags = 0xaa; // actually something meaningful
// etc etc
TableEntry pingpong_buf[2][256];
TableEntry* current_buf = pingpong_buf[0];
// Public and Private Methods that operate on FooManager
// ...
};
FooManager singleton_foo_manager;
Now actually I have a templated singleton class but that isn't necessary in this description. The point is that this is a very concisely defined statically allocated singleton class, not much code is needed.
When I try to replicate this in Rust, I run into a bunch of problems:
- C++ generates default constuctors for initializing the member values, based on the values specified in the struct/class definitions. Rust does not.
- Initialization of statically allocated array of arrays of structures is therefore childplay. In Rust it requires generating Traits, and it isn't real convenient for statically allocated data. I've tried. It's just a lot more code, and frankly I'm not sure exactly why it needs to be that way.
- If I try to macroize this for convenience, I'm probably going to run into situations where debugging the code is confusing because the code is generated at compile time.
Since this singleton pattern is pretty standard in our code, I'll need to go through this for every struct that needs to be initialized, with additional overhead if it needs to go into a static array.
So here's my question - am I missing something huge here? Because if not, it's feeling like Rust is creating inconveniences for this type of programming, even if it is providing incredible value in safety and functionality for larger applications (safety, generic collections, the whole Enum thing which is pretty cool, polymorphism through Traits, no more header files, a new build system, and a truly powerful Macro facility). I'd like to convince my embedded team that moving to Rust is a big win in both safety and convenience, but on the convenience score for small embedded apps like ours, I can't even convince myself.
But I want to be wrong!!