4 ms·
> It might look better if you split it in lines: You and other Rust enthusiasts are missing the point. In C or any C-based language or even Python, you can ju
by xinayder 3y ago
> It might look better if you split it in lines:
You and other Rust enthusiasts are missing the point.
In C or any C-based language or even Python, you can just call either a function passing the reference to the GPIO pointer, or just GPIO |= 1.
You can't do that in Rust, or no one has shown a much better way to set a bit in Rust without writing verbose code that has dozens of borrowings done.
- masklinn 3y agoThat’s not a point being missed. The comment you reply to lays out explicitly why all these components are here, and it’s the same sort of thinking which leads to the results of TFA. Your reply amounts to “I don’t wanna tho”.
- rgoulter 3y agoAs I understand the it: OP picked upon a sophisticated example that's multithreaded; the example shows off some fancy Rust techniques which deals with this complex case. It's 'fair' to say that OP's code snippet looks complex. But, it's not 'fair' to treat OP's snippet as if it's how one must write a simple blinky program. And it's good to explain that the code is complex because it's dealing with a complex case. I'd somewhat acknowledge the frustration, though. If you're trying to learn something and you take a wrong step or look in the wrong place, you don't have the intuition to figure out "here's what you want to be doing instead".
- arlort 3y agoUnless you're overloading the |= to death that assignment isn't checking any of the things that are being checked (either at compile or runtime) in the rust code This specific example is from the concurrency chapter so it's already handling multiple threads and that's why all those checks are needed. This is a random example I found to blink a led on a rp2040: > let mut led_pin = pins.gpio25.into_push_pull_output(); > > loop { > led_pin.set_high().unwrap(); > delay.delay_ms(500); > led_pin.set_low().unwrap(); > delay.delay_ms(500); > } And this is how to do it using embassy, which is an async framework for embedded in rust: https://github.com/embassy-rs/embassy/blob/main/examples/rp/src/bin/blinky.rs https://github.com/embassy-rs/embassy/blob/main/examples/rp/...
- mgoetzke 3y agoAs a newcomer to Rust (specifically Async/Threads) why are their multiple runtimes and thus different code for embedded async/threads between desktop and embedded ? I know it isn't in the standard library, so things just happened, but there is still no clear winner that does it all ? How can we make code be portable (e.g node.js like) between embedded and other platforms if they differ so much?
- steveklabnik 3y agoThere is no one set of tradeoffs that you can perfectly make across situations. Each have different needs, and so have executors that provide for those different needs.
- arlort 3y ago> How can we make code be portable (e.g node.js like) between embedded and other platforms if they differ so much? By embedded here we are talking about running bare metal on single core microcontrollers with kilobytes of memory and megabytes of storage which are more interested in driving or reading gpio/adc pins than serving a rest api or rendering a webpage To answer your question, you can't because the difference in code comes from huge differences in the use case and requirements. And trying to force using the same runtime for anything from webservers with dozens of processors and hundreds of gigabytes of ram to the kind of device that barely draws any power at all is going to make no one happy I only have a passing knowledge of embedded programming but it already seems to me that being able to effectively use this kind of abstractions in this domain is already halfway through a miracle, and probably only possible because the rust team decided to decouple the async model from a specific runtime
- nicoburns 3y agoYou absolutely can do that in Rust. Rust has raw pointers, and they can be used just like C. It might look something like: *GPIO |= 1; Where GPIO is a pointer that's mapped to a register. There's almost certainly some Rust code doing just that somewhere down the call stack in your code above. You just don't get a lot of the safety benefits that come with Rust if you do it like that (although you do get some), so a lot of the library ecosystem has invested in higher level wrappers. The motivation for doing so is good, but IMO it's been taken a little too far and led to things being over-abstracted. I suspect this will even itself out as embedded Rust matures.
- Measter 3y agoA few years ago, just after rustc got AVR support, I decided it'd be fun to port a (very) simplistic roguelike, writing everything from the HAL up. What I ended up doing was representing the MMIO registers and their bits using unit structs, and then defining and implementing some traits to tie it all together. I ended up with usage code looking like this (configuring the i2c bus): // Set the SDA and SCL pins to input, and enable the internal pullups. DDRC::clear_bits(DDRC::DDRC4 | DDRC::DDRC5); PORTC::set_bits(PORTC::PORTC4 | PORTC::PORTC5); // Initiliazing the prescaler to 1, and bit rate for a 400KHz transmission rate. TWSR::clear_bits(TWSR::TWPS0 | TWSR::TWPS1); TWBR::set_raw_value(TWI_BIT_RATE); // Enable the TWI module, and the ACK. TWCR::set_value(TWCR::TWEN | TWCR::TWEA); While my representation doesn't protect against races or aliased access, I did manage to get it to protect against reading write-only bits, writing read-only bits, and using bits with the wrong register, all at compile time. The error messages were a bit unpleasant, but I was quite happy with how it turned out. I could have implemented the *Assign operators for the register structs, but I'm not a huge fan of those. I feel it's a bit too opaque and error-prone, and preferred the clear function names. One thing that complicates matters is that you need to do volatile read and writes, not just standard pointer access. So using raw pointers, while possible, actually becomes kinda verbose.
- xinayder 3y agoThis is the whole point the author was trying to make. As you mentioned and made clear, the implementations in other languages barely have any checks for memory safety. In Rust, you can do that but you have to write a verbose code. Why not just abstract this on the SDK level and provide a way so that if you call *GPIO |= 1; it will already do that for you? Or as others mentioned, make a macro? I believe this is the point the author was trying to make. It's not about the niceties Rust have that make it complicated, it's the fact that you do have to write a whole lot more code for a simple task.
- oneshtein 3y agoJust write "unsafe" and use debugger.
- epilys 3y ago> You and other Rust enthusiasts are missing the point. Please, there's no need to make this "us vs them" :) I explained what's happening without saying it's the best approach possible. > In C or any C-based language or even Python, you can just call either a function passing the reference to the GPIO pointer, or just GPIO |= 1. You can do this in Rust just fine by using raw pointers (references). The point is, in complex programs (not the example from the embedded rust book necessarily) it's possible to make memory safety mistakes. While in Rust it is not unless you choose so. This is not about which approach is better, it's a matter of fact. Whether you prefer the one or the other is your personal choice.