Could use clarification on generics and trait bounds within a basic abstraction

Thanks for taking the time, this seems like it should be simple but I must be miss understanding how something works.
I have two methods, one taking the concrete type and the other taking two generics:

struct DCMotor {
  //...

  fn high_speed<I: SliceId, C: ChannelId>(
    &mut self,
    enable : &mut Channel<Slice<I, FreeRunning>, C>
  ) { enable.set_duty_cycle(HIGH_SPEED) . unwrap(); }

  fn low_speed(
    &mut self,
    enable : &mut Channel<Slice<Pwm1, FreeRunning>, B>
  ) { enable.set_duty_cycle(LOW_SPEED) . unwrap(); }

}

low_speed with concrete type compiles, but high_speed with generics doesn't.

I thought the inclusion of <I: SliceId, C: ChannelId> should restrict the potential types used in any calling code:

  motor.high_speed::<Pwm1, A>(channel_a);
  motor.low_speed(channel_b);

Specifying types (Pwm1../A|B) that (seem to) implement the traits SliceId, and ChannelId:

impl SliceId for Pwm1
impl ChannelId for A

I think SetDutyCycle is implemented for Channel:

impl<S: AnySlice> SetDutyCycle for Channel<S, A>

But compilation still complains the trait bounds are not satisfied:

error[E0599]: the method `set_duty_cycle` exists for mutable reference `&mut Channel<Slice<I, FreeRunning>, C>`, but its trait bounds were not satisfied
   --> src/main.rs:93:14
    |
93  |   ) { enable.set_duty_cycle(HIGH_SPEED) . unwrap(); }
    |              ^^^^^^^^^^^^^^ method cannot be called on `&mut Channel<Slice<I, FreeRunning>, C>` due to unsatisfied trait bounds
    |
   ::: /home/c/.cargo/registry/src/index.crates.io-6f17d22bba15001f/rp2040-hal-0.10.2/src/pwm/mod.rs:606:1
    |
606 | pub struct Channel<S: AnySlice, C: ChannelId> {
    | --------------------------------------------- doesn't satisfy `_: SetDutyCycle`
    |
    = note: the following trait bounds were not satisfied:
            `rp_pico::rp2040_hal::pwm::Channel<Slice<I, rp_pico::rp2040_hal::pwm::FreeRunning>, C>: SetDutyCycle`
            which is required by `&mut rp_pico::rp2040_hal::pwm::Channel<Slice<I, rp_pico::rp2040_hal::pwm::FreeRunning>, C>: SetDutyCycle`

What am I missing, how could I satisfy the compiler while still retaining the slight abstraction?


In attempts to flesh out the solution for anyone looking, as alluded to the generic S is ok but the channel is the concrete A|B.

In the rust book there is a section on the where clause and its ability to express more abstract types directly.
Where clauses - Rust By Example

// Because we would otherwise have to express this as `T: Debug` or 
// use another method of indirect approach, this requires a `where` clause:
impl<T> PrintInOption for T where
   Option<T>: Debug {
   // We want `Option<T>: Debug` as our bound because that is what's
   // being printed. Doing otherwise would be using the wrong bound.
   fn print_in_option(self) {
       println!("{:?}", Some(self));
   }
}

Applying the same logic here:

  fn high_speed<I, C> (
    &mut self,
    enable : &mut Channel<Slice<I, FreeRunning>, C>
  )
  where I: SliceId,
        C: ChannelId,
        Channel<Slice<I, FreeRunning>, C> : SetDutyCycle {
    enable.set_duty_cycle(HIGH_SPEED) . unwrap();
  }

We can call the function as initially intended and keep the generic abstraction:

  motor.high_speed::<Pwm1, A>(channel_a);
  motor.high_speed::<Pwm1, B>(channel_b);

The trait is implemented for specific types named A and B, not generic types that implements ChannelId

1 Like

Cheers, could have maybe done with a little bit more but that was enough.
Thanks again.