This Year in Embedded Rust
blog.rust-embedded.org
blog.rust-embedded.org
1. A huge chain of dependencies. Security issues aside, I prefer my embedded code to be lean and clean.
2. A big departure from how things used to be done in this world. For example interrupt handlers almost look like AWS lambda code to me. Maybe that's the future, I don't know. But right now I am not comfortable with this way of doing low level coding.
But I am keeping an open mind and will probably try it again soon.I can see myself using Rust instead of C in more projects, but maybe first after things calm down a bit. I want my development environment outdated and boring, a.k.a. "stable".
I myself rather take the latter, at least the risk of not compiling successfully is lower and the chance of getting something done is higher. Security issue aside, there are crate scanners that exactly prevents this, and how can you do that with C?
Also, embedded code looking like Lambda code is a very good thing: both embedded system and Lambda are focused on doing one thing and do its best
Edit: We have not deployed rust to safety critical yet, I am unaware of any certification that would allow that existing for any version of the rust compiler.
I assume a reference to tools that help manage potential issues around dependencies, e.g.:
* https://github.com/rustsec/rustsec/tree/main/cargo-audit
* https://github.com/EmbarkStudios/cargo-deny
"[cargo-audit] Audit Cargo.lock files for crates with security vulnerabilities reported to the RustSec Advisory Database."
"cargo-deny is a cargo plugin that lets you lint your project's dependency graph to ensure all your dependencies conform to your expectations and requirements." e.g. license, security advisories, source.
I think there is another side to this coin though. IMO there are too many embedded shops that overlook reliable 3rd-party code in favour of spending man-hours growing their own code from seed instead. Or if not that you spend days debugging some greybeard coworker's "Oh I think I wrote something like that 25 years ago for a PDP-11, I'll just email it to you.."
But to be honest I don't really disagree all that much -- As much as the embedded world needs to keep up with the times, there's no way that the level of audit required for a reliable embedded project just can keep up with how quickly crates are being changed.
> 2. A big departure from how things used to be done in this world.
Sometimes I cringe too when takes a massive server board with 9999GB of RAM and a 4g modem and calls it "embedded" just because they screwed it to the side of a helicopter... But if the root question is "will rust be viable on a puny AVR" I hope that the answer is definitely yes.
There's still plenty of lingering issues (to name just the most glaring, you can't use a compiler since nightly-2021-01-07), but you certainly can use Rust for AVR. I've been writing all my ATtiny projects in Rust for the past year. I've been very satisfied with the ability to write high-level code (zero-sized types are a wonder!) that compiles to assembly more minimal than I'd write by hand.
I can envision a future where clean abstractions are written that can provide an Arduino-like experience in Rust, but we are still a long way from there.
You are making me want to write some Rust AVR tutorials. Once we get the latest compiler building AVR code again, that will be my next focus.
I can really recommend getting a micro:bit board and following the discovery book [1].
There are a few other books in the embedded Rust bookshelf [2].
Otherwise, I wrote a bunch of sensor drivers and simple examples for several boards: [3]
[1]: https://docs.rust-embedded.org/discovery/index.html
I might also play with ARM a bit, I have a few black pills that hopefully will be easier to get started with, now that I think about it.
Why should the processing power matter for whether something is embedded or not? To me that just seems like cost optimization for an embedded project.
I think it's possible to accidentally use that straw man to justify fears when looking at embedded rust.. But as other people have pointed out in this thread, just because rusty interrupt handlers look weird doesn't work any better or worse than one written in C.
And personally I never run Nightly, I did AoC for example entirely in stable because I don't feel like I need more excitement in my life.
The "it looks alien" thing (which is how I read your comment about "looks like AWS lambda code to me") I think is a place where Godbolt can help you if you're serious enough about low-level to be at least confident reading assembler even if you never write any. Matt Godbolt built Compiler Explorer's ancestors because he knew that nicer C++ iterators claimed to have the same performance characteristics as the good-old C style for loops he'd grown up with but he needed to be sure that was a fact before he moved vital performance critical software to them. Compiler Explorer shows that yup, you get equivalent assembler, the machine is doing the exact same work as before. I think you'll find Rust's "like AWS lambda code" for interrupt handlers is eventually doing the same low-level bit twiddling you're used to writing, but presented in a different (safer) way.
FWIW HN formats the space-prefixed paragraphs in your text as fixed pitch like code, which is annoying for people on small devices when you aren't actually showing code or diagrams where spacing is important to understanding.
Where does your limit go for that? I wrote a eurorack modular embedded thing on Teensy 4.0 (imxrt) in Rust this year. I just inspected the dep tree, it's 38 crates, with 1 being my own.
https://gist.github.com/algesten/9617fc795f2fc94009f1ed311b1...
It was a joy to write this in Rust, I look forward to my next project. This was done with "stable" (not nightly).
I went into embedded because I really despise the code churn of higher level frameworks/languages. "We just released Qt5! Good luck rewriting your code."
Sadly quite often it's only new and shiny to sell something :/
I assume in embedded this is simply different, because the needs are others.
Also, you need to be careful about what you implement yourself and what you import from crates. Otherwise it will be qt5 all over again.
Are you talking about breaking changes in the compiler after Rust 1.0? That should be very rare, and generally easy to fix (e.g. by adding a few type annotations).
Or did you use unstable features? (Not sure if embedded is usable without unstable nowadays)
I haven't really seen that happen with post-1.0 Rust, which is everything onwards of early 2015. I believe they've since tightened a couple of edge cases due to soundness issues, but nothing worse than that.
Would be interesting to hear about your contrary experiences.
See: https://doc.rust-lang.org/book/appendix-07-nightly-rust.html
A related issue is that for many companies "stable" means the version shipped with Ubuntu LTS.
You can reproduce this yourself with:
git clone https://github.com/cortex/ripasso.git
cd ripasso
git checkout release-0.5.1
cargo build --locked
On my arch system this fails to build with the same errors as the bug report i got, and my rustc version is: $ rustc --version --verbose
rustc 1.57.0 (f1edd0429 2021-11-29)
binary: rustc
commit-hash: f1edd0429582dd29cccacaf50fd134b05593bd9c
commit-date: 2021-11-29
host: x86_64-unknown-linux-gnu
release: 1.57.0
LLVM version: 13.0.0I think it was something as fundamental as Error being changed or moved around.
Then, you may have configured warnings to result in compilation errors in your build/project, however, I would argue this situation is not what most people would understand as "code not compiling due to a compiler update".
I don't remember and it doesn't really matter other than we had build issues and it was unexpected.
I think you should stick with C if this is a hard goal. Let Rust settle for some years before going all in.
Conditional compilation in Rust can be done per module, so in theory you can have different archs in different .rs files, each imported conditionally, but that then seems to mean duplication of things like structs, method/function signatures in different implementations, which is pretty annoying in many cases (i.e. you'd just want the inner bits of functions to be different, or optionally call certain subsets for certain archs). You can abstract some of this to 'common' modules to a limited degree, but not much in my experience, and that comes with additional 'plumbing' complexity anyway.
Rust has cfg attributes, and the cfg_if crate which in theory is a bit closer to the pre-processor functionality (and to a degree allows nesting/cascading like in C/C++), but in my experience these are just as annoying and limiting but in different ways: it's a macro, which is annoying in other ways (needs braces, has some issues with formatting, etc).
cfgs also seem to be exclusive, so I've found it incredibly messy getting a balance right between code re-use for common parts (i.e. function/method signatures) which need to be shared by multiple cfgs, and duplication.
Maybe I'm missing something?
See here: https://doc.rust-lang.org/rust-by-example/attribute/cfg.html
It's do-able with cfg_if crate macro, but I really don't think much of the result in many complex situations, compared to what can be done in C/C++.
Also, using the cfg! macro (which you need to do to use it in logic) I think is stripped out at link time (I had all sorts of issues with this), so if you've got intrinsics which don't compile on the current platform, that's not helpful (maybe I did something wrong here, but I've googled it a lot, and asked for help several times on the Rust Discord server).
There are many Rust libraries that support dozen different hardware architectures using conditional compilation.
Rust has many pain points, but conditional compilation isn’t one.
What ever weird constraint or desire you have, a macro would solve it nicely for your case.
Most of the crates which I've seen which do that kind of thing in my experience seem to use features or conditional modules, which as I've discussed above have other downsides. Others like Vek seem to just get LLVM to do the work.
I think that's a bit disingenuous: I've certainly found Rust's infrastructure in this area quite limiting and a pain point for myself.
Take a look at the socket2 crate as a nice way to deal with different platforms.
The rule of thumb I would use is encode logic as much as possible into traits, and then abstract out specific arch details like fields etc. You can combine conditional compilation units with trait based support as well to reduce the annotations and instead provide functionality only for types that implement certain traits.
Why not just use it as-is for Rust ? There are plenty of non-C/C++ things which use cpp. X11 uses it for config files (.Xresources for instance afaik). I think that polkit or something like that uses it too for its config files. I've seen it used in Java.
A specific example is likely to lead to a more educational discussion in terms of learning about a solution to issues that you've observed or highlighting currently unsolved issues with Rust's implementation.
Either of which would be valuable.
[1] https://reviews.llvm.org/D114611
But are there any vendors actively participating in or supporting Embedded Rust?
For example is any vendor porting drivers to native Rust?
That's kind of an interesting question.
While obviously vendor participation can lead to some positive outcomes; my impression is some part of the motivation for Rust targeting the embedded space (and not waiting on vendors) is to avoid reliance on hardware vendors.
Due largely to the poor reputation hardware vendors have in regard to anything in the software tool/firmware space within the C ecosystem.
There are also a large number of things going on in industry that aren’t yet truly public. Some of these are the “I know something I can’t say” variety and some are the variety of “wow another car manufacturer has posted an embedded Rust job?”
Exiting times ahead.