2017-03-03 35 views
11

Piszę funkcję, która może zwrócić kilka z wielu różnych błędów.Jak definiujesz niestandardowe typy "Error" w Rust?

fn foo(...) -> Result<..., MyError> {} 

Prawdopodobnie będę musiał zdefiniować mój własny typ błędu, aby reprezentować takie błędy. Jestem zakładając, że będzie to enum możliwych błędów, niektóre z enum wariantów mających dane diagnostyczne z nimi związane:

enum MyError { 
    GizmoError, 
    WidgetNotFoundError(widget_name: String) 
} 

To najbardziej idiomatyczne sposób się do tego zabrać? I jak mogę wdrożyć cechę Error?

+1

Przypuszczam, że już przeczytałeś [odpowiednią sekcję z książki] (https://doc.rust-lang.org/stable/book/error-handling.html#defining-your-own-error-type). :) Ale biorąc pod uwagę wiele błędów w tym zakresie, odpowiedzi mogą chcieć rozwinąć więcej niż tylko ten wpis. –

Odpowiedz

18

Wdrażasz Error dokładnie tak, jak robiłbyś any other trait; nic o niej bardzo szczególny:

pub trait Error: Debug + Display { 
    fn description(&self) -> &str; 

    fn cause(&self) -> Option<&Error> { ... } 
} 

description metoda jest wymagane, cause ma domyślną implementację oraz preferowany typ musi także wdrożyć Debug i Display, ponieważ są one supertraits.

use std::error::Error; 
use std::fmt; 

#[derive(Debug)] 
struct Thing; 

impl Error for Thing { 
    fn description(&self) -> &str { 
     "Something bad happened" 
    } 
} 

impl fmt::Display for Thing { 
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 
     write!(f, "Oh no, something bad went down") 
    } 
} 

fn main() {} 

Oczywiście, co Thing zawiera, a tym samym implementacje metod, w dużym stopniu zależy od tego, jakie błędy chcesz mieć. Być może chcesz umieścić w nim nazwę pliku, a może jakąś liczbę całkowitą. Być może chcesz mieć enum zamiast struct, aby reprezentować wiele typów błędów.

Jeśli skończy się zawijanie istniejących błędów, zalecane jest wdrożenie From, aby przekonwertować te błędy i błąd. Dzięki temu można używać try! i ? i mieć dość ergonomiczne rozwiązanie.

Czy to jest najbardziej idiomatyczny sposób na zrobienie tego?

Idiomatycznie, powiedziałbym, że biblioteka będzie mieć małą (może 1-3) liczbę głównych typów błędów, które są narażone. Prawdopodobnie są to wyliczenia innych typów błędów. Dzięki temu konsumenci skrzyni nie mogą poradzić sobie z eksplozją typów. Oczywiście zależy to od interfejsu API i od tego, czy ma sens scalanie niektórych błędów razem.

Inną rzeczą, na którą należy zwrócić uwagę, jest to, że jeśli zdecydujesz się osadzić dane w błędzie, może to mieć daleko idące konsekwencje.Na przykład biblioteka standardowa nie zawiera nazwy pliku w błędach związanych z plikami. Takie działanie spowodowałoby dodatkowy napływ do każdego błędu pliku. Wywoływacz metody zwykle ma odpowiedni kontekst i może zdecydować, czy ten kontekst musi zostać dodany do błędu, czy nie.


Polecam zrobić to ręcznie kilka razy, aby zobaczyć, jak wszystkie elementy idą w parze. Kiedy już to zrobisz, będziesz zmęczony robieniem tego ręcznie. Następnie możesz sprawdzić skrzynie, takie jak quick-error lub error-chain, które udostępniają makra, aby zmniejszyć wartość zadaną.

Moja preferowana biblioteka jest szybkie błędów, więc oto przykład użycia że z oryginalnego typu błędu:

#[macro_use] 
extern crate quick_error; 

quick_error! { 
    #[derive(Debug)] 
    enum MyError { 
     Gizmo { 
      description("Refrob the Gizmo") 
     } 
     WidgetNotFound(widget_name: String) { 
      description("The widget could not be found") 
      display(r#"The widget "{}" could not be found"#, widget_name) 
     } 
    } 
} 

fn foo() -> Result<(), MyError> { 
    Err(MyError::WidgetNotFound("Quux".to_string())) 
} 

fn main() { 
    println!("{:?}", foo()); 
} 

Uwaga Usunąłem zbędne Error przyrostek na każdej wartości typu wyliczeniowego. Często można też wywołać typ Error i zezwolić konsumentowi na prefiks typu (mycrate::Error) lub zmienić jego nazwę podczas importowania (use mycrate::Error as FooError).

0

Czy to jest najbardziej idiotyczny sposób, aby to osiągnąć? I jak zaimplementować cechę błędu?

Jest to popularny sposób, tak. "idiomatyczny" zależy od tego, jak mocno wpisane są twoje błędy i jak chcesz, aby to współdziałało z innymi rzeczami.

A w jaki sposób mogę wprowadzić cechę błędu?

Ściśle mówiąc, nie musisz tutaj. Możliwe, że współdziałanie z innymi rzeczami wymagać będzie Error, ale ponieważ zdefiniowałeś swój typ zwracania jako wyliczenie bezpośrednio, Twój kod powinien działać bez niego.