Bare Metal Rust Generics
ecorax.net
ecorax.net
Rust appears to provide the ability to largely move to a world where the bugs are in logic and not in buffer/array verification or multi-threading. It feels like a real advancement to move up a layer of abstraction from plumbing to design.
Time will tell, certainly it’s not impossible to have bugs in rust by any stretch of the imagination, but I have found that bugs in rust code tend to be more interesting than “out of bounds memory exception at line X.”
The compiler can do some optimizations like this, yes, but there's a lot more work to do in this area.
As an example of something folks still do by hand sometimes, take the starts_with method on Path: https://doc.rust-lang.org/stable/std/path/struct.Path.html#m...
It looks like this:
pub fn starts_with<P: AsRef<Path>>(&self, base: P) -> bool {
self._starts_with(base.as_ref())
}
fn _starts_with(&self, base: &Path) -> bool {
iter_after(self.components(), base.components()).is_some()
}
Why split this into two functions? Well, you can get smaller code size this way, because you're sort of "hand-de-duplicating" the parts that aren't generic. Once we call .as_ref, everything else is actually identical, but the compiler isn't good enough at this yet to do this itself, so we do it by hand.I wouldn't say this technique is super super common or well-known, just for the standard library (because it's used everywhere) and for folks that are sensitive to code size, like embedded or wasm people.
It is true that dyn means you don't get monomorphization, and can help with binary sizes.
EDIT: thinking about this some more, I wanted to say that it does feel similar, but one big difference that’s easy to explain is that dyn will change the way the value is represented in memory, and this will not. Conceptually, both do “cast and then call this single function”, but in the dyn case, the cast would be to a trait object, whereas this casts directly to &Path.
In the next part of the article I'll hopefully explore some drawbacks of choosing generics all the way down. Either driver would've likely perform a little better if developed fully independently, but by a smaller margin that I would've expected.
For example, see how switching from a concrete to a generic type increases the size of clone() by 4x: https://rust.godbolt.org/z/qbYr3v
This is because at the point the compiler synthesizes clone() it has less information about the type.
It's a bit of a chicken-and-egg problem. To monomorphize clone(), someone must emit an implementation first. But an optimal implementation requires analyses that aren't available until later in the pipeline. Here the optimization kicks in for types deriving Copy, but a generic parameter is enough to defeat it.
I'd be interested in an example where LLVM can't optimize the general version, as it means we might want to do this through MIR shims instead (which can be generated when collecting the monomorphic instances to codegen - this is what happens when you clone a tuple or closure, for example).
The link you provide shows only one function, because LLVM has optimized both to be identical, and deduplicated them.
(If you disable the "Directives" filter, you can see a `.set example::clone_concrete, example::clone_abstract`, which aliases one to the other)
The output with 1.45.2 is as follows:
example::clone_concrete:
mov eax, edi
ret
example::clone_abstract:
mov ecx, edi
and ecx, -256
xor eax, eax
xor edx, edx
cmp dil, 1
sete dl
cmove eax, ecx
or eax, edx
retRight at the beginning this is a great quote:
"I was wrong."
We'd all do well to say that more often when it happens, and that's not about Rust, just life generally.
The "just so"-bit is really hard.
Embedded systems need something like Rust.
I took a class in it at one of the RustConf's and that made me feel, even as a novice in the embedded space, that I would be able to do some great things. If I remember correctly the class was on an M0 board.
What is needed is to get chip manufacturers onboard - and to make them understand that they need to take software seriously. Right now the situation is a bit depressing. The ones that are ahead of the pack choose based more on politics and less based on technical merit. And most see this as a "solved problem" because people are provably able to write software for their devices.
A lot of embedded developers don't really care that the toolchains are awkward because it "works for them". Which isn't helpful. And people easily get annoyed if you suggest that "perhaps this could be done less clumsily". There really isn't any easy way to say "this needs improvement" without provoking a defensive response. And people tend to be fairly frustrated when they get to the point where they reach out to get some help to solve problems.
I still know of companies that skip using any RTOS and create their own OS-like infrastructure because the existing offerings are just too much of a time-waster. (Try to use Zephyr for a year on fairly new chipsets. The amount of time spent getting builds to work after upgrades is astonishing and reveals just how sketchy and fragile the tool chains are - which gives even seasoned embedded-devlopers used to sketchy and/or expensive tool chains pause).
There have been "bare metal rust" initiatives for a while, but I haven't seen any that have gotten critical mass yet. Or buy-in from the hardware industry. I'm not sure why. I suspect the relative newness of Rust might be a factor. Perhaps people that have invested decades in C/C++ are afraid of starting over with a new language because they want to stick to what is familiar.
I think we need to understand better what is needed to make this a success. I'm tempted to think that starting with the tooling might be a good idea. And assume that it needs to support multiple "kernels" (that might not even need compatible interfaces).
One thing that is a bit interesting to look at is the difference in how Espressif and ARM have approached this. Espressif devices became popular in hobbyist segment where Arduino (or rather Wiring) was the "gold standard". Rather than fight it, they embraced and supported it. In not that many years this familiarity lead to lots of commercial grade stuff being made with Espressif chipsets - because they are cheap and there's lots of solutions on the web that can get you prototyping fast. This lead to widespread use of their devices and people "graduating" to using their FreeRTOS-based SDK. An SDK that now has pretty impressive support for anything from Audio to motor control.
(As for ARM: their tooling and them kind of restarting mBed years ago and causing confusion, regressions in tooling usability etc made me ditch them and look elsewhere. Since the "professionals" didn't even use mBed it just wasn't worth the investment in time)
With regard to tooling, it is worth looking at Platformio and ponder why there is a need for a tool to manage embedded toolchains - and what functionality the tool provides. Platformio is perhaps the most convenient system I've used for managing embedded projects. It still has some shortcomings, but it is miles beyond where the industry is right now.
But to be fair, it has been a while since I used mBed and it might be time to give it another spin just to know the lay of the land.
If they were to dedicate serious resources to making Rust run on M0 and up, I'd be really, really thankful.
So yeah, they've given us hardware to be able to run CI, which is a huge step. This also includes statements like "Arm intends to further donate newer and more capable hardware to this initiative."
They've had employees doing testing and submitting patches and fixing bugs.
The focus is on aarch64-unknown-linux-gnu to start, but there's a lot of procedural kinks to work out. Future targets should go much more smoothly.
More detail here: https://github.com/rust-lang/rfcs/pull/2959
https://www.nordicsemi.com/Products/Low-power-cellular-IoT/n...
This library gets a lot of attention in the embedded rust community and is designed for tiny devices like an arm M0 mcu.
https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html#usin...
If your c++ code uses the c ABI (application binary interface) then you can call it easily too. There is a tool called rust-bindgen which will automatically generate the required rust code from header files.
Remember that a lot of embedded systems run on really constrained chipsets where memory is measured in kilobytes and where the underlying architecture isn't necessarily a von Neuman architecture.
There are people agreeing with you, and they are trying to make it happen: https://ferrous-systems.com/blog/sealed-rust-the-plan/
Ada and Rust don't have 1:1 comparison on their safety guarantees.
Rust makes a lot of guarantees around memory safety that Ada does not, whilst Ada is far more focused on correctness. Whilst related, these are very different.
If you're using Ada without SPARK, it will try and enforce these same guarantees, but generally with a runtime contract system, rather than compile time.
But projects tend to have a longer shelf-life. Contracts expected to take a decade to complete aren't unusual, and the product can be expected to be in use for twice that, or longer.