Рекомендуется возвращать Result <_, impl Error>, а не Result <_, & str> в Rust? - PullRequest
0 голосов
/ 12 января 2019

Можно ли практиковать этот стиль Result?

fn a() -> Result<u32, &'static str>

А какова цель ошибки? https://doc.rust -lang.org / станд / ошибка / trait.Error.html

Является ли лучше результат Impl Error Result?

impl Error for MyError {..... }
fn a() -> Result<u32, MyError>

Ответы [ 2 ]

0 голосов
/ 12 января 2019

Короче говоря: нет, это не хорошо. String as error отбрасывает информацию о деталях и причинах, делая эту ошибку бесполезной для вызывающей стороны, так как она не может проверить ее и, возможно, восстановить после нее.

В случае, если вам просто нужно заполнить параметр Error чем-то, создайте структуру модуля. Это не очень полезно, но и не так сильно, как строка. И вы можете легко отличить foo::SomeError от bar::SomeError.

#[derive(Debug)]
pub struct SomeError; // No fields.

Если вы можете перечислить варианты ошибок, используйте enum. Иногда полезно также включить в него другие ошибки.

#[derive(Debug)]
pub enum PasswordError {
    Empty,
    ToShort,
    NoDigits,
    NoLetters,
    NoSpecials
}

#[derive(Debug)]
pub enum ConfigLoadError {
   InvalidValues,
   DeserializationError(serde::de::Error),
   IoError(std::io::Error),
}

Никто не мешает вам использовать struct с. Они особенно полезны, когда вы намеренно хотите скрыть некоторую информацию от вызывающей стороны (в отличие от enum, чьи варианты всегда имеют публичную видимость). Например. вызывающий не имеет ничего общего с сообщением об ошибке, но может использовать kind для его обработки:

pub enum RegistrationErrorKind {
    InvalidName { wrong_char_idx: usize },
    NonUniqueName,
    WeakPassword,
    DatabaseError(db::Error),
}

#[derive(Debug)]
pub struct RegistrationError {
    message: String, // Private field
    pub kind: RegistrationErrorKind, // Public field
}

impl Ошибка - экзистенциальный тип - здесь не имеет смысла. Вы не можете возвращать разные типы ошибок с ним в месте ошибки, если это было вашим намерением. И непрозрачные ошибки не очень полезны, как строки.

std::error::Error черта гарантирует, что ваш тип SomeError имеет реализацию для std::fmt::{Display, Debug} (для отображения ошибки пользователю и разработчику, соответственно) и предоставляет некоторые полезные методы, такие как source (Это возвращает причину этой ошибки); is, downcast, downcast_ref, downcast_mut. Последние 4 относятся к стиранию типа ошибки.

Ошибка типа стирания

У стирания типа ошибки есть свои недостатки, но также стоит упомянуть.

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

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

use std::error::Error;
use std::fmt;
use std::fs;
use std::io::Error as IoError;
use std::net::AddrParseError;
use std::net::Ipv4Addr
use std::path::Path;

// Error for case where file contains '127.0.0.1'
#[derive(Debug)]
pub struct AddressIsLocalhostError;

// Display implementation is required for std::error::Error.
impl fmt::Display for AddressIsLocalhostError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Address is localhost")
    }
}

impl Error for AddresIsLocalhostError {} // Defaults are okay here.

// Now we have a function that takes a path and returns 
// non-localhost Ipv4Addr on success.
// On fail it can return either of IoError, AddrParseError or AddressIsLocalhostError.
fn non_localhost_ipv4_from_file(path: &Path) -> Result<Ipv4Addr, Box<dyn Error + 'static>> {
    // Opening and reading file may cause IoError.
    // ? operator will automatically convert it to Box<dyn Error + 'static>. 
    // (via From trait implementation)
    // This way concrete type of error is "erased": we don't know what's
    // in a box, in fact it's kind of black box now, but we still can call
    // methods that Error trait provides.
    let content = fs::read_to_string(path)?;

    // Parsing Ipv4Addr from string [slice] 
    // may cause another error: AddressParseError.
    // And ? will convert it to to the same type: Box<dyn Error + 'static>
    let addr: Ipv4Addr = content.parse()?;

    if addr == Ipv4Add::new(127, 0, 0, 1) {
        // Here we perform manual conversion 
        // from AddressIsLocalhostError 
        // to Box<dyn Error + 'static> and return error.
        return Err(AddressIsLocalhostError.into());
    }

    // Everyhing is okay, returning addr.
    Ok(Ipv4Addr)
}


fn main() {
    // Let's try to use our function.
    let maybe_address = non_localhost_ipv4_from_file(
        "sure_it_contains_localhost.conf"
    );

    // Let's see what kind of magic Error trait provides!
    match maybe_address {
        // Print address on success.
        Ok(addr) => println!("File was containing address: {}", addr),
        Err(err) => {
            // We sure can just print this error with.
            // println!("{}", err.as_ref());
            // Because Error implementation implies Display implementation.
            // But let's imagine we want to inspect error.

            // Here deref coercion implicitly converts
            // `&Box<dyn Error>` to `&dyn Error`.
            // And downcast_ref tries to convert this &dyn Error
            // back to &IoError, returning either
            // Some(&IoError) or none
            if Some(err) = err.downcast_ref::<IoError>() {
                println!("Unfortunately, IO error occured: {}", err)
            }

            // There's also downcast_mut, which does the same, but gives us
            // mutable reference.
            if Some(mut err) = err.downcast_mut::<AddressParseError>() {
                // Here we can mutate err. But we'll only print it.
                println!(
                    "Unfortunately, what file was cantaining, \
                     was not in fact an ipv4 address: {}",
                    err
                );
            }

            // Finally there's 'is' and 'downcast'.
            // 'is' comapres "erased" type with some concrete type.
            if err.is::<AddressIsLocalhostError>() {
               // 'downcast' tries to convert Box<dyn Error + 'static>
               // to box with value of some concrete type.
               // Here - to Box<AddressIsLocalhostError>.
               let err: Box<AddressIsLocalhostError> = 
                   Error::downcast(err).unwrap();
            }
        }
    };
}

Подводя итог: ошибки должны (я бы сказал - должен) предоставлять полезную информацию вызывающей стороне, кроме возможности просто отображать их, поэтому они не должны быть строками. И ошибки должны реализовывать Error как минимум, чтобы сохранить более-менее последовательный опыт обработки ошибок во всех ящиках. Все остальное зависит от ситуации.

Кайо уже упомянул Книгу ржавчины.

Но эти ссылки могут быть также полезны:

std :: любая документация API уровня модуля

std :: error :: Ошибка документации API

0 голосов
/ 12 января 2019

Для простых случаев использования достаточно непрозрачного типа ошибки, например Result<u32, &'static str> или Result<u32, String>, но для более сложных библиотек полезно и даже рекомендуется создавать собственный тип ошибки, например struct MyError или enum AnotherLibError , который поможет вам лучше определить свои намерения. Вы также можете прочитать главу Error Handling книги Rust by Example.

Черта Error, являющаяся частью std, помогает разработчикам общим и централизованным образом определять свои собственные типы ошибок для описания того, что произошло, и возможных основных причин (обратная трассировка). В настоящее время он несколько ограничен, но есть планы помочь улучшить его удобство использования .

Когда вы используете impl Error, вы говорите компилятору, что вас не волнует возвращаемый тип, поскольку он реализует черту Error. Этот подход полезен, когда тип ошибки слишком сложен или когда вы хотите обобщить тип возвращаемого значения. E.g.:

fn example() -> Result<Duration, impl Error> {
    let sys_time = SystemTime::now();
    sleep(Duration::from_secs(1));
    let new_sys_time = SystemTime::now();
    sys_time.duration_since(new_sys_time)
}

Метод duration_since возвращает тип Result<Duration, SystemTimeError>, но в приведенной выше сигнатуре метода вы можете видеть, что для части Err результата он возвращает все, что реализует Error черта характера.

Подводя итог, если вы прочитали книгу Rust и знаете, что делаете, вы можете выбрать подход, который наилучшим образом соответствует вашим потребностям. В противном случае лучше всего определить свои собственные типы для своих ошибок или использовать некоторые сторонние утилиты, такие как error-chain или failure ящики.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...