Как мне обернуть несколько обобщённых типов ошибок вокруг пользовательского перечисления ошибок в Rust

Вопрос или проблема

Как мне обернуть несколько универсальных типов ошибок в пользовательский enum Error в Rust. Это довольно просто, я просто хочу, чтобы это работало.

enum Error<SPIE, PINE> {
  SpiError(SPIE),
  PinError(PINE),
  // другие пользовательские ошибки
}
impl<SPIE,PINE> From<SPIE> for Error<SPIE,PINE> {
  fn from(value: SPIE) -> Self {
      Self::SpiError(value)
  }
}
impl<SPIE,PINE> From<PINE> for Error<SPIE,PINE> {
  fn from(value: PINE) -> Self {
      Self::PinError(value)
  }
}

Вместо этого компилятор жалуется на:

конфликтующие реализации трейта `From<_>` для типа `Error<_, _>`

что я понимаю, но, возможно, я могу как-то различить два типа…


Основываясь на принятом ответе, в итоге я использовал это:

use Error::Pin;
enum Error<SPIE, PINE> {
  Spi(SPIE),
  Pin(PINE),
  // другие пользовательские ошибки
}
impl<SPIE, PINE> From<SPIE> for Error<SPIE,PINE> {
  fn from(value: SPIE) -> Self {
      Self::Spi(value)
  }
}

и отображал ошибки так: ce.set_low().map_err(Pin)?;

Это не может работать, потому что нет способа ограничить SPIE и PINE быть разными типами. Если вы попытаетесь преобразовать Foo в Error<Foo, Foo>, какую реализацию From следует использовать?

Я не совсем уверен, что есть лучший подход, чем просто непосредственно конструировать вариант enum. Если ваша цель — иметь возможность использовать ? в функции, то вы можете просто предшествовать этому вызовом map_err как .map_err(Error::SpiError)?, чтобы обернуть ошибку в вариант SpiError.

Два момента, которые могут сделать это даже более эргономичным:

  • Имя SpiError избыточно, если вы уже находитесь в типе с суффиксом Error — почему бы не enum Error<SPIE, PINE> { Spi(SPIE), Pin(PINE) }?
  • Вы можете use Error::*;, чтобы импортировать варианты напрямую.

Совмещая оба этих момента, предыдущий вызов map_err можно сократить до .map_err(Spi)?. Это не автоматически, но это не так уж много шаблонного кода — я использую именно этот подход постоянно.

Ответ или решение

Чтобы обернуть несколько типов ошибок в пользовательский enum в Rust, необходимо учитывать несколько аспектов, связанных с обобщенными типами и реализациями трейтов. В этом ответе мы обсудим, как правильно это сделать, избегая конфликтов в реализации, и предложим оптимальный подход.

Проблема с конфликтацией реализаций

Когда вы пытаетесь реализовать трейты From<SPIE> и From<PINE> для вашего enum Error<SPIE, PINE>, возникает проблема, связанная с тем, что компилятор не может определить, какой из вариантов From следует использовать, если оба типа одинаковые. Например, если вы попытаетесь обработать ошибку типа Foo, Rust не сможет решить, учитывать ли это как SpiError или PinError, если оба типа являются Foo. Это приводит к конфликту реализаций.

Решение проблемы

Наиболее простым и эффективным решением этой проблемы является использование различных типов для обертки ошибок. Вы можете переименовать ваши варианты в Spi и Pin, исключив префиксы Error. Это позволит избежать путаницы и улучшит читаемость кода. Давайте рассмотрим пример:

enum Error<SPIE, PINE> {
    Spi(SPIE),
    Pin(PINE),
    // Другие пользовательские ошибки
}

impl<SPIE, PINE> From<SPIE> for Error<SPIE, PINE> {
    fn from(value: SPIE) -> Self {
        Self::Spi(value)
    }
}

impl<SPIE, PINE> From<PINE> for Error<SPIE, PINE> {
    fn from(value: PINE) -> Self {
        Self::Pin(value)
    }
}

Использование

С такой реализацией вы можете удобно преобразовывать ошибки с помощью метода map_err, что делает обработку ошибок очень удобной:

// Пример использования
fn some_function() -> Result<(), Error<MySpiError, MyPinError>> {
    // Предположим, что ce.set_low() возвращает Result<(), MyPinError>
    ce.set_low().map_err(Error::Pin)?;
    Ok(())
}

Зачем это нужно?

Использование enum для управления ошибками позволяет вам создавать более управляемые и понятные структуры ошибок в вашей программе. Это особенно важно в больших проектах с множеством различных условий ошибок. Четкая иерархия ошибок улучшает отладку и поддержку кода.

Заключение

Обертывание нескольких типов ошибок в собственный enum в Rust может показаться сложной задачей из-за ограничений компилятора относительно реализаций трейтов. Однако, следуя приведенному примеру и рекомендациям, вы сможете легко решить эту проблему, обеспечив при этом читаемость и удобство использования вашего кода. Исключая ненужные префиксы и используя метод map_err, вы сделаете свой код более лаконичным и понятным.

Оцените материал
Добавить комментарий

Капча загружается...