5 ms·
Sure I can parse Rust. But it is definitely more complicated, and more "noisy". I'm dabbling in some microcontroller stuff currently. The one thing I've noti
by inferiorhuman 7y ago
Sure I can parse Rust. But it is definitely more complicated, and more "noisy".
I'm dabbling in some microcontroller stuff currently. The one thing I've noticed is that the Arduino (C++) environment seems to rely on magic. Lots of mysterious constants, registers, etc and it's not entirely clear what's what.
Meanwhile using rust in this environment is very explicit. It's quite a bit more verbose than the C++ version. I'm also sure some of this is due to me having to write the implementation itself but for me it's a lot easier to understand what's going on when things are nicely typed. It's the difference between:
pinMode(LED_BUILTIN, OUTPUT)
which can be expanded to:
PIO_Configure(
g_APinDescription[ulPin].pPort,
(g_pinStatus[ulPin] & 0xF0) >> 4 ? PIO_OUTPUT_1 : PIO_OUTPUT_0,
g_APinDescription[ulPin].ulPin,
g_APinDescription[ulPin].ulPinConfiguration ) ;
g_pinStatus[ulPin] = (g_pinStatus[ulPin] & 0xF0) | PIN_STATUS_DIGITAL_OUTPUT;
and
let mut pioc = p.PIOC.split(&mut pmc);
let mut blue = pioc
.pc25
.into_peripheral_b(&mut pioc.absr)
.into_push_pull_output(&mut pioc.mddr, &mut pioc.oer);
expanded to (I used a proc macro here):
absr.absr().write(|w| w.#accessor().set_bit());
oer.oer().write_with_zero(|w| w.#accessor().set_bit());
Specifically I really like that I can access the parts of the register by name and that access is typed. You can't write to a read-only register, you can't modify a write-only register, and if you don't have a default value defined you call something else (e.g. write_with_zero) that makes it clear what you're doing.
Edit: Another thing I really prefer over the rust vs Arduino/C++ API is that state is encoded in types. So you may have a GPIO pin PC25. But that type takes a type parameter indicating state e.g. PC25<PeripheralB<Output<PushPull>>>. Yeah that's verbose but it's also very explicit. If you have something (e.g. UART/USART driver) that needs a pin to be configured in a specific manner you'll have to go through some non-trivial effort to pass an incorrectly configured pin in. As a result if your program compiles you can be more confident that it will do what you expect.
- liamdiprose 7y agoI love the typed API svd2rust generates as well. Generally you can just let autocomplete do the driving, the only things needing a brain are the abbreviated register names manufacturers use and the order of operations needed. I wonder if Rust would be better suited for Arduino/embedded beginners. Rust is quite painless when you just want to glue a few crates together. I'm sure everyone would rather debug a compiler error than some invalid memory issue happening on the microcontroller.
- Avamander 7y agoThe "mysterious constants, registers, etc" are very readable once you get familiar with the agreed abbreviations and the MCU you're programming for (and how bit shifts work in the code example you gave). I really disagree that Rust is somehow more readable, especially not the example you brought. How can you say `pioc.oer` is somehow more understandable than any piece of that C code?
- inferiorhuman 7y agoHow can you say `pioc.oer` is somehow more understandable than any piece of that C code? Easily. pioc.oer tells you that you're using the PIOC peripheral, oer tells you're your manipulating the oer register specifically, and the mutable reference (&mut) indicates that you're modifying it (and the borrow checker ensures you're not going to be modifying it in two places at once). Additionally the rust embedded folks have a practice of returning a "constrained" structure from an initialization function. Typically the configuration function will take ownership of the peripheral and then return a restricted wrapper around it. This means that if you're doing something that will result in immutable registers you'll get back a structure that doesn't allow you to modify those registers. So, for instance, trying to configure the watchdog timer twice on the MCU I'm using will not compile because you don't even have that original object around anymore. If the program compiles you're probably OK. Nowhere in that C code is any of that referenced. There's no idea which peripheral is being manipulated. ulPin and ulPinConfiguration both expand to integers, so there's no guarantee you've even gotten the parameters in the right order. Likewise the shifting and masking is exposing unnecessary implementation details, and things that compile don't necessarily do what you think they may do (e.g. modifying immutable registers).
- Avamander 7y ago> There's no idea which peripheral is being manipulated. The Arduino-y function usually maps internal pin mappings (and to the pin numbers on the given PCB. It's quite clear if you know what you're compiling the code for. Not to mention that how a pin-remapping function works usually doesn't matter, the resulting abstraction is very nice to use. > pioc.oer tells you that you're using the PIOC peripheral, oer tells you're your manipulating the oer register specifically And why should I care about that information when I already have the abstraction written before? The Rust code is much much worse in terms of ease-of-use in this case - manually having to look up how pins on the board map to internal registers is cumbersome. Some of the confusion about the names might also stem from that I expect consistent capitalization when dealing with registers, why would `pioc` be lowercase if it's in reality a register being modified? It's actually weird. I won't even begin how horrible-looking the "expanded" form is, compared to the 2-line C equivalent. One more thing I just now realized, the Rust team made an incredibly bad decision picking the symbols, for example for mutable references. I don't see anyone with any good amount of C experience ever wanting to use Rust if they have to re-learn what `&` really means - useless waste of time for most. It's akin to designing a new safer bike but switching the handlebar direction. And in the end, the amount of symbols in Rust, combined with how annoying they're to type on non-US layouts, combined with the (wrongly) carried over connotations from other languages makes it a terrible replacement for what it's advertised for. > So, for instance, trying to configure the watchdog timer twice on the MCU I'm using will not compile because you don't even have that original object around anymore. That is very cumbersome and illogical. Reconfiguration is quite common. > There's no idea which peripheral is being manipulated. I think that's just your unfamiliarity with the platform and the example you chose. If you'd write the exact same code you brought as an example in C it'd be much clearer than the Rust code and just two lines. I'd love to see the asm of the Rust code.