Why can't the compiler automatically resolve this trait?


#1

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?


#2

I think the issue is your IntoAlternate trait doesn’t associate the generic type parameter to the output - they’re two different types:

pub(crate) trait IntoAlternate<Mode> {
    type Regs;
    type Output;
    fn into_alternate(self, regs: &mut Self::Regs) -> Self::Output;
}

Mode is not related to Output in any manner, and compiler cannot disambiguate which Mode you’re after. Perhaps the trait doesn’t need the Output associated type:

pub(crate) trait IntoAlternate<Mode> {
    type Regs;

    fn into_alternate(self, regs: &mut Self::Regs) -> Mode;
}

Now the output of the fn is associated with the Mode generic type param, and this should allow the compiler to tie things together.