Hacker News new | ask | show | jobs
by Kostarrr 646 days ago
It's really cool that google is investing so heavily in rust. However, the blog post is more of a "starting point" than a "lessons learned" (which I hoped for).
3 comments

I feel like it's a poor starting point, too. embedded-hal or embassy would be a much better starting point. Ultimately if you know enough to know what a memory mapped register is, you can _probably_ become dangerous with Rust really quickly. The alternative is someone using Arduino libraries, who would greatly benefit from a HAL of some kind.

What I found is that the Rust HALs are incredibly thin and understandable, I was digging into MCU datasheets within a month of first using embassy (with no prior embedded experience).

It's not a great title for the post, because it's ambiguous and could go either way, but the post itself doesn't pretend to be anything other than a how-to documentation guide for Android integrators.
In some sense it is bit missleading as well. On firmware level, you typically need to use a lot of unsafe code. Of course, this highlights the potential danger zones very well, if you use it correctly. But there is still chance that human made a mistake that leads into memory bug. It should be addressed, while it greatly reduces the risks.
I'm hacking on a firmware in Rust right now, for the ESP32-C3, and the only unsafe bits are buried deeply in the esp-hal crate for the C FFI to ESP-IDF and low level hardware twiddling. esp-hal exposes safe interfaces to all of that, so I've had to write zero unsafe code.
But someone else just has written that unsafe code. It is there and unsafe is used as intended, to create an abstraction layer and limit the risky area. Note that even if you use safe abstractions, interactions through safe abstractions can leak to issues in unsafe code.
This may come as a shock, but it's actually unsafe all the way down. Turns out everything needs to become machine code eventually, and CPUs can do all kinds of nasty stuff. Doesn't make containing the unsafe codes to a region of the program any less of a good idea.
I was just about to edit my comment, because indeed that is the case, and someone will grab into it. But firmware as a term includes typically writing also that part which parent used ”safely”. In Rust world hal is own term and as abstraction more known term, but that is not the case if you advertise Rust in blog in order to make non-Rust users to make an understanding. If you build your own products, there is no hal, you need to write it. And use of unsafe to limit the dangerous code is indeed excellent practice.

But why leave that part out from the blog post which gets a lot of audience?

If interactions through safe abstractions leak unsafety into your code, that's a bug in the abstractions.

Edit: who voted me down? This is literally the definition of writing a safe abstraction around unsafe code in Rust. If the user of your safe abstraction can trigger a segfault or UB or otherwise do something that is supposed to require unsafe, then your safe abstraction is buggy.

Memory safety and UB sure you could argue for that. But take for example something like FD reuse. In Rust's std library there's abstractions to prevent that and encourage ownership semantics even though there's no segfault or UB that will happen as a result. So clearly "safety" is a spectrum here. And it should be pretty obvious from the existence of unit tests and various property testing frameworks & sanitizers that bugs exist that aren't memory safety / UB related can't be completely prevented through Rust's abstractions.
Safety may be a spectrum, but `unsafe` in Rust is very explicit about what it allows/enables. They never say "if you only write safe Rust you won't encounter logic bugs".
> If interactions through safe abstractions leak unsafety into your code, that's a bug in the abstractions.

This isn't much different than saying that C safe language if you write it perfectly.

> definition of writing a safe abstraction

That definition is not guaranteeing safety, because that is usually not possible. It just about limiting risky areas.

> This isn't much different than saying that C safe language if you write it perfectly.

The difference is in C, the entire language is unsafe, whereas in Rust only the bits marked `unsafe` are unsafe. Most Rust code does not need to use `unsafe` at all, and by extension most Rust developers don't need to touch `unsafe`. And for those developers who do use `unsafe`, instead of having to prove every single line of the program is safe like you do in C, you only have to prove that the tiny subset of the program contained within `unsafe` is safe.

No, not quite. If the abstractions leak then it's Rust's fault. If you write buggy C then it's your fault.
There's a huge gap between:

1. this is nasty 2. this is so much better 3. this is perfect

writing C is (1), most people claim that Rust is (2). (3) is utopia and anyone who claims that is not being honest.

I have seen this argument SO many times. I don't understand why this is so hard to understand? Do C folks use mmap directly instead of malloc? Does "safe" or "sensible" abstraction not exist in C? Do you use void* everywhere because "safe" abstraction like struct S* simply doesnt exist and if they do exist then "well, it could be wrong anyway so why not just give up?"

The problem with manual memory management is that it makes safe and easy abstraction a lot harder. Every non-trivial function has a set of rules on who owns what that only exist in comments (or worse, in someone's head). Using the function wrong won't cause a compile error, but random unsoundness (that is sometimes data/dataflow dependent so pretty much impossible to sanitize for).
There is Rust code I saw that was cluttered with "unsafe block" to an extend that I am a bit skeptical there was meaningful memory safety left.. There is C code which is nicely structured where I have more confidence in. I am also skeptical about the overall complexity of Rust and I think cargo is "nasty" because of supply chain risk, proliferation of dependencies. So I do not believe for a second the gap between C and Rust so big Rust proponents want us to believe.
> There is Rust code I saw that was cluttered with "unsafe block" to an extend that I am a bit skeptical there was meaningful memory safety left.. There is C code which is nicely structured where I have more confidence in.

This is anecdotal but I have seen FAR more nasty C code than I have Rust. I can probably count using all my fingers and toes the number of times I have seen and have to vet unsafe code.

I spent a week vetting a WebSocket implementation in C to not have buffer overflows, memory leak, use-after-free, overflow, etc etc before I even start vetting the logic. They also have their own bespoke async library so I have to make sense of that first too.

I spent a day looking at WebSocket implementation in Rust exactly because I don't have to worry about stuff like is this void* reliable and following the call stack to make sure it makes sense.

> I am also skeptical about the overall complexity of Rust and I think cargo is "nasty" because of supply chain risk, proliferation of dependencies

I am curious how C solves this problem, if you need a btree, a JSON parser, a tree-sitter, a string with SSO optimization; where do you get this? Write your own? Vendor some packages? Rely on the package manager?

They all have the same issue of there's simply too many code to vet.

> There is Rust code I saw that was cluttered with "unsafe block" to an extend that I am a bit skeptical there was meaningful memory safety left..

There certainly is. But is it more than 1% of the Rust code out there?

And let's assume we're talking about a project where it is more than 1%. Let's say there's only 50% safe code (I personally have never seen this, not even in embedded). Is that still not a strict improvement over C?

> I am also skeptical about the overall complexity of Rust

When you're building a small project, using something like C++ or Rust will indeed feel like that.

But when you're building a large project, then using a simple language doesn't make things simpler... it simply moves the complexity elsewhere, and more specifically, into your code.

In that case, consolidating the complexity into a single place (the language) makes it so that this complexity is reusable, you learn it once and you understand all places that use it, even in other projects.

The time it takes to learn, yes, it'll be huge. Afterwards, programming in Rust becomes significantly easier than in C.

> I think cargo is "nasty" because of supply chain risk, proliferation of dependencies.

This one I actually really agree with, and am very sad about it.

I invite you to look at how close the C proponents think the gap is.
ESP-IDF is a pretty high-level framework on top of FreeRTOS. Both do the vast majority of the heavy lifting. Once you're working on bare metal without these fancy frameworks, things get real hairy real fast.

I strongly encourage you to poke around in the lower levels of the IDF. It's equal parts fascinating and horrifying.

Espressif's code is... interesting, to say the least.

> Espressif's code is... interesting, to say the least.

Of that, I have no doubt. I'm anxiously awaiting esp-openmac's completion, so that I can use Wifi without the ESP-IDF as a dependency. It's the only reason I pull that crate in, because I'm aware of the FreeRTOS dependency.

In my experience, one never really has to work at the bare metal layer in Rust, unless you are designing new chips yourself. All the existing microcontrollers I would think of using already have well worn hals. Truly impressive what the Rust community has accomplished.

Yes, on hal level there will be a bunch of unsafe. On firmware application level it's not often I find the need to reach for unsafe though. It would typically be if the hal is lacking in some sense.
I was a bit taken aback when I realized that Embassy's low-level PAC crates expose all of the devices raw registers through "safe" interfaces though. I'm pretty sure that letting you configure DMA engines arbitrarily at any time is not in the spirit of safe Rust. AFAIK their high-level HAL crates do aim to be properly safe but their PAC approach makes me nervous.

https://docs.embassy.dev/rp-pac/git/default/dma/struct.Chann...

Usually the HAL is between the main firmware and the PAC, so whether the PAC methods are marked as unsafe could almost be considered an implementation detail.

But yes, there has been a lot of discussion around how to handle DMA peripherals - the embedded_dma crate offers some abstractions that I've found handy.

But firmware is kinda generic term that usually includes the hal. I am not sure what it meant in this blog post.