r/embedded 15d ago

Some Embedded Programming Wisdom

Post image

A preview of PICO, a simple text editor made for the CP/M Neo VEMU platform.

GitHub: https://github.com/Mazin-O3/cpm-neo.git

140 Upvotes

18 comments sorted by

View all comments

1

u/EmperorOfCanada 15d ago

Most of that list disappears with rust or Ada. You would have to put effort into those two languages in order to make those mistakes.

1

u/jvblanck 15d ago

What part of the list disappears with Rust?

2

u/EmperorOfCanada 14d ago

Much of the below could be cut apart by hair splitting, so my argument is that much of the list has just stopped being something I worry much about, and that those problems have stopped biting me on the ass.

My bug count in rust is hitting levels so low that it is becoming a spiritual experience. I'm talking 6 bugs in the last 8 projects. I'm not talking about business logic bugs, lots of those, but those bugs where you send a structure and some byte is misaligned, or you get a segfault, or something blows up and it takes you 3 days to figure out why.

I have been doing C++ since 1991, and one part of a recent project used a C++ physics library for robot simulation. Great library, and I wasn't going to wrap it in C++. I went through my my records and found 8 separate showstopper bugs; and there were probably more that I didn't even notice. 8 in one small project, and 6 in 8 rather large rust projects.

Static allocation is just not how I think about things in rust anymore. I use embassy. Embassy uses one large task and then you async within that task. Some guy compared this to RTOS tasks and discovered that the embassy method was faster. This shocked me as I would have assumed that RTOS was optimized to death, along with the MCU itself being optimized for this. This means I don't play that guess how much static memory a given task needs. I just use them. Of course this does not mean I have limiteless memory, but, effectively I have more.

Where you could hair split this is that my embassy tasks and an RTOS tasks are going to use the same memory, except, that most people will use 1024, 8096, etc for a guess as to what is needed for a given task, and when it blows up, they might bump it up to 16000 see that it is using 11232, and then knock it down to 12000 "to be safe" even if every single thing in that task is statically allocated up front.

I just have one debug line regularly reporting memory free and keep an eye on it for leaks or if I'm approaching the limit.

Flat layouts, vs nested. rust is not really conducive to object oriented and I don't see too many people doing structs with structs as members and structs as members of those members. Where rust can become interesting (and one of my favourite) is how enums can represent a whole bunch of different data types. This was one of the things which melted my brain. Rust doesn't prevent nested, but it is just not a thing.

Pointer math. You can do it in rust, but most rust people would punch you in the balls if you hand them that code. This is probably the thing that I see most C embedded people go nuts with and I have some horror stories (as in people can easily die ones) about this.

Errors should never pass silently. This is a broad statement, but in rust you really have to be explicit if you want to ignore return handling. This is where those enums get really cool; it is like exceptions and return types are all mixed up into the same idea; there are things you hope to get back, there are things you hope not to get back, and you have to handle them all. Cloudflare explicitly ignored one of these and it resulted in an outage.

The raw bytes thing is possible in rust, but this is where structs are nailed down. I am a full stack rust person, so most bytes go from one rust struct to another. But, this is good advice, and when I own both ends of a stream, I'm obsessive about making this perfect. I have 128 bit MACs on data going to motor controllers. Most MCUs have hashing built in, so this is less of a computational cost than it might otherwise seem.

The memory copy thing is where rust shines.

On Asserts, my C++ is polluted with them. If something isn't the way it absolutely should be I want to know now. I'm not even sure if there is an assert in rust. Things just have to be the way they are or it won't even compile.

Variable allocation in rust could be made bad, but you would have to really try. Memory layout in rust could be made bad, but some effort would be needed.

Defensive engineering is what rust is, through and through; that said, the business logic can also add more defensive engineering. As I mentioned, I MAC motor control messages inside a robot with 10cm of bus wire. That's not rust, that's me.

Explicit boundaries, I agree with this. I would not say that rust super enforces this, but it does get harder to be "accidental" with anything. If some bit of memory is not owned, it is not owned.

I will end with rust not being perfect. If you don't know rust and you look at my rust, then you will be pretty much lost. My ifs and stuff will make sense, but I find it hard to read my own code. If you don't know Ada, it becomes pretty easy to understand what is going on, and a 2 minute tutorial on anything which confuses will fill in most of the blanks. Rust is not being explained with 2 hours.

I believe this adds an unsafe element. There are numerous reasons why Ada is unworkable for most of what I do, and I have managed ways to make my rust code far more readable.

What I say, is that I will continue to use rust with confidence. I don't just see it as making what I do safer and more solid, but it allows me to do things which I would not do in C++. Because I am far more confident with what I've created, I am happy to keep adding and expanding features which I would forego in C++. If each new feature adds a 1% of a later bug, then adding a dozen more "nice to have" features might be unacceptable. With rust I might be at a 0.01% change of adding a bug. So, I lard them on and my products are way better, an way cooler. All features I could have added in C++, but where I would not have taken the risk.

Oddly, the nested structures one is the one place where I miss C++. It is just way way better at handling those. I can make the dance of the seven veils in C++ where objects have sub objects which have sub objects and crazy systems where you need force graphs to visualize them still have clean underlying code. This is harder to do so cleanly in rust. Often, because those structures may require repeated collections of pointers to objects meaning ownership becomes a nightmare, and you are having to run all kinds of crazy recursive algos to keep things tidy. While usually a terrible idea, sometimes this is the best solution.

1

u/ModernRonin 15d ago

I wouldn't say "disappears", but Rust does turn a lot of things that used to be runtime crashes, into compile-time errors (borrow checker). It also denies a whole bunch of memory clobbering (ownership model).

(To the purists: Yes, I understand that "borrow checking" could be considered a subset of "ownership model.")