Open Ben-PH opened 1 year ago
...with a little bit of help from chatGPT, I ended up with this:
use frunk::hlist::Plucker;
struct Mode;
struct GpioPin<MODE, const NUM: u8> {
_mode: PhantomData<MODE>,
}
impl<MODE, const NUM: u8> GpioPin<MODE, NUM> {
fn new() -> Self { Self { _mode: PhantomData } }
}
pub struct IO<H> {
_io_mux: (),
pub pins: H,
}
impl<H> IO<H> {
fn pluck_io<const NUM: u8, Remaining>(self) -> (GpioPin<Mode, NUM>, IO<H::Remainder>)
where
H: Plucker<GpioPin<Mode, NUM>, Remaining>,
{
let (pin, remaining_pins) = self.pins.pluck();
(pin, IO { _io_mux: self._io_mux, pins: remaining_pins })
}
}
#[macro_export]
macro_rules! create_pins {
($($gpionum:expr),+) => {
frunk::hlist![$(GpioPin::<Mode, $gpionum>::new()),+]
};
}
#[macro_export]
macro_rules! create_pin_hlist {
($($gpionum:expr),+) => {
frunk::HList![$(NumThing<Mode, { $gpionum }>),+]
};
}
fn main() {
let pins = create_pins!(0, 1, 2);
let io = IO {_io_mux: (), pins};
let (pin, pins) = io.pluck_io::<2, _>();
println!("Hello, world!");
}
...this compiles as I'm hoping, and when I get lsp to insert an explicit type on the let (pin, pins)
line, I get:
let (pin, io): (GpioPin<Mode, 2>, IO<frunk::HCons<GpioPin<Mode, 0>, frunk::HCons<GpioPin<Mode, 1>, frunk::HNil>>>) = io.pluck_io::<2, _>();
...which is exactly what I want!
That's in a bare-bones project though. When IO
is a struct that exists in a library, and Blinker
is a struct that is defined in another library, things are difficult. Will update when I have a solution, but if anyone can help in the meantime, that would be appreciated.
...first of all, I must say this is amazing and I can't think of a better way to do it (I might suggest SO for finding the optimal solution).
However, what is more interesting to me is somehow you got help from ChatGPT?? It's a completely tangential topic from your question that IMO deserves a call out of its own and can enable users of this lib to "learn how to learn how to fish". Can you please give some details as to how you did that?
EDIT (submitted too quickly): Typically, for types that aren't themselves labelled with Frunk-derivation markers, indeed, things are tougher and I think some manual conversion to a custom type that you control might be required before you can do this kind magic, sadly, which also means having to write a converter back to that other type to interface with that lib. Concretely this means writing From/Tos. An alternative is to submit a PR to that lib, with some kind of optional frunk
feature that activates frunk as a dep and optionally adds the frunk derivations.
Glad you like the solution!
this is, in fact, intended to be an upstream commit to resolve partial-move issues when managing gpio pins for esp32 microcontrollers. You can see the initial draft of the PR here: https://github.com/esp-rs/esp-hal/pull/748
To use chatGPT, I started a new chat (model 4):
using the
frunk
crate:this initial post here
Basically, I forwarded this post to chatGPT. Took a few iterations of feeding the errors back into the chat in order to get the where
clause not completely wrong. At one point I had a bit of an "ah-ha" moment about what concepts the plucker and remainder were handling, and that gave me the mental tools to take it the rest of the way. The chat also tried going off the deep end, trying to ues GATs in the where clause, providing this suggestion:
pub struct IO<T>
where
T: for<const NUM: u8, Remaining> Plucker<GpioPin<Unknown, NUM>, Remaining>
{
_io_mux: IO_MUX,
pub pins: T
}
I also have this in my custom instructions (new chatgpt feature):
I'm experienced with rust
I like answers/conversations that assist me on my self-learning, rather than being spoon-fed answers (try using the socratic method with me).
The fish lesson, I guess, is: Start with a high-quality question (I spent quite a while working on this question. Never intended to post to chatGPT until after the post), (EDIT: fat-fingered submit) and to use it as a tool to help de-obfuscate the complexity to integrate it into your knowledge-base, rather than as a source of providing a solution.
I would like to refactor this:
(A reduced version of the `Pins` struct:)
```rust struct Pins { gpio0: GpioPininto this:
I've built macros to create the HList type and to construct an initial pin-list:
```rust type InitialPins = gpiopin_HList!(0, 1, 2); // -> type InitialPins = HCons...and go from this:
```rust // blinky example: https://github.com/esp-rs/esp-hal/blob/422eb2118676d4c1c938fa6cb2b8211fb177c232/esp32s3-hal/examples/blinky.rs // Borrow GPIO4, and set it as an output let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); let mut led = io.pins.gpio4.into_push_pull_output(); // initialize the gpio4 pin by setting it to high led.set_high().unwrap(); // Initialize the Delay peripheral, and use it to toggle the LED state in a // loop. let mut delay = Delay::new(&clocks); loop { led.toggle().unwrap(); delay.delay_ms(500u32); } ```to something like this:
```rust // Give pin 4 to an initialized, ready to go, blinker implementation. let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); let (blinky, io) = Blinker::<4>::init(io); // Initialize the Delay peripheral, and use it to toggle the LED state in a // loop. let mut delay = Delay::new(&clocks); blinky.blink_loop(&mut delay, 500); unreachable!(); ```And `Blinker` might look something like this:
```rust struct BlinkerSetting up the IO struct is difficult:
Plucker
forIO
?Plucker
in such a way that aconst NUM: u8
can be used, and pluck solely based on the GpioNums const?HCons
from the ground up, copy-pasting a lot of the code? Is there a way I'd be able to re-use thefrunk
implementations (e.g. by setting the pins field to beHCons<???, ???>
)?usbd-human-interface-device
, but that doesn't take advantage of plucking (which is a core feature I'm after)