Вопрос или проблема
Как мне обернуть несколько универсальных типов ошибок в пользовательский 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
, вы сделаете свой код более лаконичным и понятным.