Custom error return: expected type parameter `E` found enum

Hi,
I am trying to implement a new sensor for embedded purpose and I wanna check it can be talked with the sensor (get_device_id()) and if the correct sensor is connected (device_detected). If an interface error occurs, write_read returns the error, but if the sensor responds but the device id is incorrect, I wanna return a different error.
Somehow I have to return I2C with a template parameter, but how can I achive this?

return Err(Error::I2C(ErrorKind::WrongDeviceId));
   |                                 ---------- ^^^^^^^^^^^^^^^^^^^^^^^^ expected type parameter `E`, found `ErrorKind`

pub struct LPS22HH<Interface, AquireMode> {
    interface: Interface,
    sa0_high: bool,
    one_shot: AquireMode,
}

#[derive(Debug)]
pub enum Error<E> {
    I2C(E),
}

#[derive(Debug)]
pub enum ErrorKind {
    WrongDeviceId,
}

// Type states for the AquireMode in LPS22HH
pub struct SingleShot;
pub struct Continuous;

/// Implementation indipendend of the aquire type
impl<Interface, AquireMode, E> LPS22HH<Interface, AquireMode>
where
    Interface: WriteRead<Error = E> + Write<Error = E>,
    E: Debug,
{
    fn address(&self) -> u8 {
        return BASE_ADDRESS + self.sa0_high as u8;
    }

    pub fn device_detected(&mut self) -> Result<(), Error<E>> {
        match self.get_device_id() {
            Ok(device_id) => {
                if device_id != DEVICE_ID {
                    // TODO: handle
                     return Err(Error::I2C(ErrorKind::WrongDeviceId));
                } else {
                    return Ok(());
                }
            },
            Err(e) => return Err(e),
        }

    }

    fn get_device_id(&mut self) -> Result<u8, Error<E>> {
        let input = [Register::WHO_AM_I.addr()];
        let mut output = [0u8];

        match self
            .interface
            .write_read(self.address(), &input, &mut output)
        {
            Ok(()) => {
                return Ok(output[0]);
            }
            Err(e) => return Err(Error::I2C(e)),
        }
    }
}

impl<Interface, E> LPS22HH<Interface, Continuous>
where
    Interface: WriteRead<Error = E> + Write<Error = E>,
    E: Debug,
{
    /// Create a new lps22hh device in continuous mode
    pub fn new_continuous(interface: Interface, sa0_high: bool) -> Result<Self, Error<E>> {
        let mut lps22hh = LPS22HH {
            interface,
            sa0_high,
            one_shot: Continuous,
        };

        match lps22hh.device_detected() {
            Ok(device_id) => {}
            Err(e) => return Err(e),
        }

        Ok(lps22hh)
    }
}

Consider removing the type parameter of Error if you only ever use ErrorKind inside of it.

Thanks I will consider it. Is this even possible without dyn Boxes?

You can separate out the idea of where the error came from and what the error kind is.

#[derive(Debug)]
pub struct Error<E> {
    source: Option<E>,
    kind: ErrorKind,
}

#[derive(Debug, Copy, Clone)]
pub enum ErrorKind {
    WrongDeviceId,
    MissingDevice
}

And add some convenience conversions.

impl<E> From<ErrorKind> for Error<E> {
    fn from(kind: ErrorKind) -> Self {
        let source = None;
        Self { source, kind }
    }
}

impl ErrorKind {
    fn with<E>(self, source: E) -> Error<E> {
        let source = Some(source);
        let kind = self;
        Error { source, kind }
    }
}

Then things can look like this.

    pub fn device_detected(&mut self) -> Result<(), Error<E>> {
        let device_id = self.get_device_id()?;
        if device_id != DEVICE_ID {
            Err(ErrorKind::WrongDeviceId.into())
        } else {
            Ok(())
        }
    }

    fn get_device_id(&mut self) -> Result<u8, Error<E>> {
        let input = [Register::WHO_AM_I.addr()];
        let mut output = [0u8];

        self.interface
            .write_read(self.address(), &input, &mut output)
            .map_err(|e| ErrorKind::MissingDevice.with(e))?;

        Ok(output[0])
    }

Reading what I posted, due_to would be a better name than with. Anyway, here's a hacked together playground.

If the associated error types have to implement std::error::Error (and preferably be 'static), you could also box them up in a Box<dyn std::error::Error> and get rid of the type parameter. Edit: But I see just now you probably don't want that.

Thank you @quinedot . I have seen this idea already, but I did not understood why they ussed it. This makes it more clear now :slight_smile: