Spyke
rustlang·The Rust Programming Languagebych00f

Traits vs generics in i2c.rs

So I'm slowly digging my way through the stm32l0xx-hal crate. As written, the crate works with an older version of embedded-hal. My ultimate goal is to bring it up to the latest version or at least start the process.

My question surrounds the implementation of the I2c struct in i2c.rs.

Starting on line 57 is a struct that contains three elements of generic type.

/// I2C abstraction
pub struct I2c<I2C, SDA, SCL> {
    i2c: I2C,
    sda: SDA,
    scl: SCL,
}

I want to specifically talk about the first element. The bounds on what the generic type can be start in the implementation where the first element must implement the type Instance.

impl<I, SDA, SCL> I2c<I, SDA, SCL>
where
    I: Instance,
{
    pub fn new(i2c: I, sda: SDA, scl: SCL, freq: Hertz, rcc: &mut Rcc) -> Self
        where
            I: Instance,
            SDA: SDAPin<I>,
            SCL: SCLPin<I>,
        {...}
...
}

Going one step higher, I2c::new() is called inside a macro on line 522 inside an implementation of the I2cExt trait on I2C1, I2C2, etc.

impl I2cExt<$I2CX> for $I2CX {
            fn i2c<SDA, SCL>(
                self,
                sda: SDA,
                scl: SCL,
                freq: Hertz,
                rcc: &mut Rcc,
            ) -> I2c<$I2CX, SDA, SCL>
            where
                SDA: SDAPin<$I2CX>,
                SCL: SCLPin<$I2CX>,
            {
                I2c::new(self, sda, scl, freq, rcc)
            }
        }

So the generic type in the I2cExt trait is the same as the type for which the trait is implemented in this macro. The <$I2CX> type is passed to the I2c return type which ultimately creates an instance of the I2c struct with the first element of type <$I2CX> and along the way, it's verified that it implements the Instance type.

So my question is: couldn't we ignore all of this generic nonsense if we just defined the I2c struct to expect a certain trait for its first value?

For example, couldn't the first line of the struct just be i2c: Instance or some other defined trait that encompasses all of the I2c drivers on the chip? If we're bounding the type to an Instance anyway, why bother with all the generics?

View original on lemmy.world
rustlang·The Rust Programming Languagebych00f

Noob rustacean with question regarding typed constructors.

Just finished chapter 10 of the Rust book and I thought I'd try digging into some real code. I'm a rust noob, but I've got 15 years with embedded C.

I'm working my way through the source code of the stm32l0xx-hal crate. Inside adc.rs on line 96, an implementation for the Adc struct is created for the type Adc<Ready> where Ready is defined on line 324.

/// Analog to Digital converter interface
pub struct Adc<State> {
    rb: ADC,
    sample_time: SampleTime,
    align: Align,
    precision: Precision,
    _state: State,
}

impl Adc<Ready> {
    pub fn new(adc: ADC, rcc: &mut Rcc) -> Self {
        // Enable ADC clocks
        ADC::enable(rcc);
        adc.cr.modify(|_, w| w.advregen().set_bit());

        Self {
            rb: adc,
            sample_time: SampleTime::T_1_5,
            align: Align::Right,
            precision: Precision::B_12,
            _state: Ready,
        }
    }
...
}
...
/// Indicates that the ADC peripheral is ready
pub struct Ready;

I'm a little confused about how the associated function new() works. I understand that it generates a new Adc<Ready>, but I don't fully understand why it's inside the impl Adc<Ready> block.

It's not a method that would be called on an Adc<Ready> type, and it doesn't take any arguments of type Ready. Couldn't it just as easily have been inside a impl<T> Adc<T> block?

Or is it that impl types go both ways. Like you can't return an Adc<Ready> unless you're inside an impl Adc<Ready>?

Sidenote: Since the Ready struct has no fields, does "Ready" actually create a new instance of a Ready type? (like you don't need Ready{}?)

View original on lemmy.world