What would constitute a "good" programming language for embedded systems?
I know that ~70% of embedded systems are programmed with C, lots of movement or at least motivation is seen on moving to Rust. My question is: why this languages are good for embedded software development? And overall what would constitute a good PL for this domain?
- Simple syntax and easy to read.
- Compiles down to efficient machine code.
- Very well documented.
- Preferably the language is close to the metal.
I avoid using C practically everywhere these days, but tend to prefer it for embedded programs for the above reasons,
Because the people who make the embedded systems SoC / SoM / etc. provide support, sample code, and IDEs/libraries for C and not for Rust.
C works well for embedded systems programming because, at heart, it's "a better assembly language". That is, it gives greater convenience and readability with minimal additional overhead in terms of binary size and speed (compared with assembly).
That said, I tend to use a small subset of C++ (combined with straight assembly for critical portions) rather than C. In essence, using C++ as a better C as a better assembly.
I think that any language that allows you pretty tight control over what the compiled code looks like, is deterministic, and compiles to something small and fast, would do for embedded systems programming. C, C++, and assembly do the job nicely for me, but is hardly the only good approach. If another comes along that is significantly better, I'll happily switch to it.
I couldn't tell you what that would look like, though, as my current toolsets aren't leaving me wanting for anything.
Yeah for the reasons you stated I would want to C, but with a better type system, no UB, some of the foot guns removed, and maybe some cool static analysis for stuff like invariants and stack size guarantees built in.
You are looking for Ada.
Tcl is another language used in the domain; it is rarely used outside of some sectors, but lots of interfaces for embedded systems are written in Tcl, especially in the Electical/Computer/Electronics Engineering sector.
IMO, the implementation of event handling is easy and simple, while being incredibly powerful. It may not fill the same niche as C does, but as a glue/scripting language, it's really good. Furthermore, the Tk library allows easy development of GUIs without much hassle.
Outside of embedded, you can also find it used for automation and testing -- SQLite famously has many millions lines of Tcl code for testing. [0]
[0] https://sqlite.org/testing.html
>> What would constitute a "good" programming language for embedded systems?
"Good" for what purpose? That is, what are the goals, requirements, and limitations of the target embedded system? If the system fails or encounters errors, what is the worst outcome?
C and C++ are the most widely used and supported. Rust is up and coming in terms of support, features, and popularity. Ada works great and is required for some embedded applications.
>> overall what would constitute a good PL for this domain?
Most embedded systems have limited hardware (slow or low power processor, minimal RAM, minimal storage, etc.) and will have either a minimal real-time operating system (RTOS) or no operating system running on bare metal.
A good programming language for embedded work will be able to interface directly with hardware, run fast, and use minimal memory.
Depending on size of the embedded system, the preconditions differs slighly; An Android phone is an embedded system, just like an 8051.
Android programs (and lots of Android itself) is written in Java, giving it memory safety, strong typing and ease of programming.
But it comes at the cost of garbage collection, requiring JIT for performance and high RAM and Flash consumption.
Those drawbacks are acceptable for use apps (just buy a new phone every 2 years), but not in the low-level, such as the Linux kernel. And straight up impossible on an 8051.
C gives the performance and low footprint to work both in a Linux environment and 8051, but it comes with the drawback of no memory safety and a lot of footguns. Performance is not just about speed and RAM/Flash requirements, but also about en energy consumption and physical size of the MCU on the PCB - both of which are increasingly important.
Enter rust: The performance and low footprint of C, with the memory safety and strong typing of Java. The lack of garbage collector is compensated by a /very/ picky compiler that makes memory management a fist-class citizen. All those problems a C developer would manually have to manage, remember and hopefully cover, are enforced compile-time.
Analysis done in large C codebases have shown that roughly half of all security vulnerabilities reported simply cannot happen in Rust because the compiler don't allow it.
Some say Rust is harder to learn, and it's harder to program in. But that is because writing bug-free programs IS hard, and the language is designed to prevent a whole class of bugs without sacrificing performance or footprint.
I think, Rust is what C would have been, had C been invented now, rather than 50+ (!) years ago.
There are only a handful of languages that are efficient enough to be suitable for embedded systems, with C and C++ being the most common. But Rust is certainly one that is very well suited for the job.
And thanks to cargo, embassy and OSes like Ariel, there is a very big set of standardised APIs and crates that makes embedded development feel just as efficient as desktop or server development.
- One that doesn't relies on code reuse. I. e. one that isn't overly verbose. One often have to reinvent the wheel, simply because no existing library fits all the requirements - like there are three that does what I want, but one uses too much memory, another busy-waits in a separate thread eating the battery, yet another one assumes the network is perfect and panics on first packet loss. All of this works on desktop because hey who cares about power and if it hangs we can always just restart the program. Also code reuse is the primary reason why one might want dynamic allocation and this is problematic.
- A runtime that doesn't allocate silently. Read "no GC and no malloc calls from library functions". It's not just that it's not enough memory, it's when we run out of memory (and it is going to happen at some point) there will be no recourse, not even a way to log what happened if the logger itself or the log delivery mechanism needs the heap. Having "allocates memory dynamically" as a part of the type/effect signature would be nice.
- Honestly, even stack allocation is sometimes problematic. Mostly we assume it's fine, but being able to statically validate stack usage would be nice.
- What I miss in C is support for stackless coroutines. Threads are expensive because of stack size and callbacks are really painful. (In a way stackless coroutines and static stack usage are related). Zig tried but afaik didn't get it yet.
- Support for compile-time metaprogramming. Code generation sucks. C preprocessors is not nearly as bad as usually painted, X-macros are really nice, except when you make an error, you get an undecipherable mess for a message. Seriously, just having a C preprocessor with a facility "expand this token from this dictionary of macros or fail if it doesn't expand" would be much nicer.
- Well-defined integer overflows, without sacrificing expression readability. Preferably ones that would allow arbitrarily-sized intermediate results and with an operator to warp/saturate on assignment, with an optional output overflow flag. For me it's much more of an issue than memory/pointer bugs.
- Support for C interoperability (including being able to import C macros) because vendors aren't going to support anything but C ever.
Zig is quite close, through is still feels a bit rough.
Assembly is the good language for embedded systems.
Allows precision and efficiency, easily work around platform bugs, don't need to fret about building compilers for them.
Embedded systems have generally either limited resources such as processing, memory, bus interfaces, or real time operations such as scheduled tasks in short periods, hard/soft deadlines. These sort of requirements address best performance and small footprint, so you mostly need programming languages that meets them.
Personally I like AVR8 assembly but the systems I make are very small.
ulisp