Background:
I'm writing some embedded code for a hardware abstraction layer that uses the type system to statically convey a lot of the hardware state.
I have a crate called gpio
which has the following declarations, among other things:
/// Alternate function 0
pub struct AF0<Mode> {
_mode: PhantomData<Mode>
}
/// Alternate function 1
pub struct AF1<Mode> {
_mode: PhantomData<Mode>
}
...lots more of these...
/// Push-Pull mode
pub struct PushPull;
/// Open-Drain mode
pub struct OpenDrain;
/// Trait implemented by pins that support being configured as an alternate function
pub(crate) trait IntoAlternate<Mode> {
type Regs;
type Output;
fn into_alternate(self, regs: &mut Self::Regs) -> Self::Output;
}
In this crate I have a macro which generates a bunch of pin structs whose names have formats like "PA0" or "PB12". I name these as $PXi
in the macro. I do the following when iterating through the various $PXi
:
/// Pin with some mode (Input<PullUp>, Output<PushPull>, AF0<OpenDrain>, etc)
pub struct $PXi<Mode> {
_mode: PhantomData<Mode>,
}
...lots of impl...
...like the functions "into_alternate_open_drain" and "into_alternate_push_pull"...
...which are private...
// Each $PXi that this macro generates has some subset of the AF* alternate functions
// that it implements. This iterates through the list.
$(
/// Implements an open drain alternate function
impl<Mode> IntoAlternate<super::$AFn<OpenDrain>> for $PXi<Mode> {
type Regs = Regs;
type Output = $PXi<super::$AFn<OpenDrain>>;
fn into_alternate(self, regs: &mut Regs) -> $PXi<super::$AFn<OpenDrain>> {
unsafe { self.into_alternate_open_drain(AlternateFunction::$AFn, regs) }
}
}
/// Implements a push pull alternate function
impl<Mode> IntoAlternate<super::$AFn<PushPull>> for $PXi<Mode> {
type Regs = Regs;
type Output = $PXi<super::$AFn<PushPull>>;
fn into_alternate(self, regs: &mut Regs) -> $PXi<super::$AFn<PushPull>> {
unsafe { self.into_alternate_push_pull(AlternateFunction::$AFn, regs) }
}
}
)*
I had hoped with this implementation that I would be able to call into_alternate
on a $PXi
instance and then using the return type it could figure out which trait implementation to use. For example, here's what I do in another module to wrap the into_alternate
call into something a little more descriptive using a macro:
// In this macro, there is a list of pins ($PXi) associated with alternate functions ($af).
$(
impl<Mode> IntoPwm<gpio::$gpiox::$PXi<gpio::$af<PushPull>>> for gpio::$gpiox::$PXi<Mode> {
type Regs = gpio::$gpiox::Regs;
fn into_pwm(self, regs: &mut gpio::$gpiox::Regs) -> gpio::$gpiox::$PXi<gpio::$af<PushPull>> {
// I now attempt to call the into_alternate method on the IntoAlternate implementation
// that matches $af.
self.into_alternate(regs)
}
}
)+
Now, this fails like so:
error[E0284]: type annotations required: cannot resolve `<gpio::gpioa::PA8<Mode> as gpio::IntoAlternate<_>>::Regs == gpio::gpioa::Regs`
--> components/stm32f031x-hal/src/pwm.rs:163:34
|
163 | self.into_alternate(regs)
| ^^^^^^^^^^^^^^
...
172 | / pwm!(TIM1, tim1, pwm1, APB2, tim1en, [
173 | | CH1: (ch1, ccmr1_output, oc1m, oc1pe, ccr1, cc1e, [
174 | | PA8: (gpioa, AF2),
175 | | ]),
176 | | ]);
| |___- in this macro invocation
When I follow the advice of the compiler and specify the trait explicitly:
fn into_pwm(self, regs: &mut gpio::$gpiox::Regs) -> gpio::$gpiox::$PXi<gpio::$af<PushPull>> {
IntoAlternate::<gpio::$af<PushPull>>::into_alternate(self, regs)
}
It compiles!!
Question:
However, I would really like to just say self.into_alternate(regs)
and have the return type disambiguate the implementations. I thought that this should work, but maybe it can't work like this? I know C# has trouble doing things like this. Is rust unable to disambiguate based on return type?