💾 Archived View for stanner.bayern › en › rewrite-in-c.gmi captured on 2021-11-30 at 20:18:30. Gemini links have been rewritten to link to archived content
-=-=-=-=-=-=-
In my bachelor's thesis, I introduced the Rust Programming language into an embedded system at the company I was working at. I was the first one to use it on an embedded platform, and was so enthusiastic about Rust! Save, super performant code. Finally an alternative to Python (which I like, but which is too slow for our intended use case) and C++ (which I hate). I'm stating this to make clear where I am coming from: Like many others who came to be sceptical of Rust, I was an enthusiast.
So, anyways, we implemented our program, which isn't that complicated: It's a C-compatible library, collecting data from applications and automatically distributing it over a TCP connection. The features you need for this are:
- Threads
- Timers
- Poll
- Sockets
It was my first (and is until today my only) Rust program and the Language was not as hard as expected, or so it seemed. The code was created and tested on local target within a few weeks. While language and syntax confused coworkers who had nothing to do with Rust (think about `as_mut()` or missing return-statements), the enthusiasm was overweighing.
Then, the task came up to port the program to an ARM64 embedded system. We use trusted build platforms which often do not have (or are not allowed to have) internet access.
So, how do you install the cross toolchain for Rust?
rustup... fetches it from the internet for you. If you have internet. And your docker-based build server does not. And it also has to reinstall the whole toolchain every time someone commits something, when the continuous integration starts up again.
How do I install the C-cross-toolchain?
`apt install gcc-aarch64-linux-gnu`
"But that needs Internet as well!" – right, it does. But in my company we have local Debian mirrors.
So, anyways, no reason to damn poor Rust, right? After all, to some degree it is our task as a company to maintain our build systems and toolchains. So, no problem, let's just store the toolchain locally and fetch it whenever necessary. So, where do I get the Rust cross toolchain?• Let's check the website for a tarball...
and the tarballs only give you the x86 toolchain. No cross building.
Ok.
Now, there is also no aarch64-rustc debian package. Hmm.
(in the end, we stored what Rustup fetches on one of our server. That's a very, very bad solution, but we couldn't help ourselves. So every time we run our CI, we download 400MB of Rust toolchain from our local server.
Keep in mind that in the industry, you can't just pay someone to do nothing else but find ways to solve problems which, in my eyes, should be solved by the language maintainers)
I needed an event loop (Async did not yet exist back then), so I used Mio. Unfortunately, I also needed timers, and pollable timers had been thrown out of Mio a while prior. So I found the package mio-extras, which had been saved by someone, and used that. A package not officially maintained anymore, removed from Mio before a replacement was ready, before Async was ready.
A few weeks after my thesis and this project had been finished, they released Async-IO.
All my code was suddenly outdated, and it is still outdated today.
I never found time to learn Async and port the program, and tbh I wasn't very motivated to begin with, since back then I started realizing that I had a hard time keeping up with all the features. For example, I had also never used traits.
I needed a client for my thesis' program and decided to write it in Go. Within a few days, I had used every single feature Go offers, except for generic interfaces.
When my coworker, a poor victim of M$, needed the client, I added one line of config and crosscompiled it for Windows.
And it just worked. Instantly.
If you read this and happen to be an enthusiast about Rust, you might say that none of this is Rust's fault. You could say that Rust was young (fair point), so of course there was only Mio back then. That the toolchain is young and it will improve over time.
Fair points, but I don't see how the situation will improve. Whenever I discussed those topics with Rust folks, I got answers like “Well, you can install it with Rustup :)” – ähm, yeah, I can...
I got, no offense intended, the impression that most Rust programmers are high level folks who are used to fiber to their desk, who aren't aware of the needs of people in the embedded industry.
Which brings us to our conclusion:
I needed polls, timers and networking. I also needed queues, which are conveniently in the Rust stdlib.
But all the primer things are available in C, and a simple linked list que is easily created. You need some experience in C to make it work, of course, but your friends are there to help you:
- The address sanatizer
- In the future, the function analyzer (in gcc)
With a bit of thinking I can write my program POSIX compliant, so at work we could quite easily port the program to systems using musl-libc or running on RISC-V or even on sel4.
I wish you much fun trying to port a Rust program to any of those systems.
So, we rewrote the whole thing in C. Cross compiling works by setting an environment variable. The C toolchain didn't vendor me programs like Fuchsia, and it did not require me to download 400MB of toolchain for every CI run.
So, yes, you could argue that many of my problems are my fault. That I'm not talented enough, that I should have just learned Async, found a smart way to port the whole toolchain.
I am, however, smart enough for C and Go. I found a smart way to crosscompile a Go program for Windows... which is just duckduckgo-ing for 30 seconds and copy pasting a snippet.
So, in the future, I'll do the Drew-Approach••: I'll keep my buffer overflows and "undefined behavior" (which I btw never personally encountered in C, since the many compilers force each other to user-sane behavior) – and as a reward I get a simple language which I can port to any existing system.
No one will remove timerfd from Linux like they removed it from Mio. No one will change the poll-API or its behavior within a few months, forcing me to rewrite my program to be up to date. No one will include a complex, gigantic feature like Async into C any time soon.
C provides everything I need.
C is the flawed, timeless language, which will always work everywhere.
An answer to rewritten in Rust
• This article btw. describes the state of the toolchain in 2019, but I hear many issues are still the same.
••