Какова правильная часть ошибки типа результата функции, распространяющей различные типы ошибок? - PullRequest
0 голосов
/ 30 мая 2020

Я попытался написать функцию, которая будет распространять разные типы ошибок. Только для примера см. Следующий код:

use std::fs::File;
use std::io;
use std::io::Read;

fn main() {
    let number = read_number_from_file().unwrap();

    println!("Read number {}!", number);
}

// So what is the correct error part of the Result<i32, ...>?
fn read_number_from_file() -> Result<i32, io::Error> {
    let mut f = File::open("hello.txt")?;
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    let number = s.parse()?;
    Ok(number)
}

Неудивительно, что его компиляция приводит к следующей ошибке:

error[E0277]: `?` couldn't convert the error to `std::io::Error`
  --> src\main.rs:16:27
   |
16 |     let number = s.parse()?;
   |                           ^ the trait `std::convert::From<std::num::ParseIntError>` is not implemented for `std::io::Error`
   |
   = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
   = help: the following implementations were found:
             <std::io::Error as std::convert::From<std::ffi::NulError>>
             <std::io::Error as std::convert::From<std::io::ErrorKind>>
             <std::io::Error as std::convert::From<std::io::IntoInnerError<W>>>
   = note: required by `std::convert::From::from`

Итак, каков правильный тип ошибки типа Result? Есть ли ответ не только на этот конкретный c случай: у меня есть функция с несколькими вызовами других функций, возвращающая Result<T, E>, которые в случае ошибки должны распространяться из функции на вызывающую, но имеют разные типы E ?

1 Ответ

1 голос
/ 30 мая 2020

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

#[derive(Debug)]
enum Error {
    Io(io::Error),
    ParseInt(num::ParseIntError)
}

impl From<io::Error> for Error {
    fn from(other: io::Error) -> Error {
        Error::Io(other)
    }
}

impl From<num::ParseIntError> for Error {
    fn from(other: num::ParseIntError) -> Error {
        Error::ParseInt(other)
    }
}

Преобразования From позволяют оператору ? работать должным образом.

Вы можете сэкономить много времени на вводе текста, используя один из нескольких ящиков, которые создадут для вас шаблон. Некоторые популярные: thiserror и snafu .

Я предпочитаю thiserror, потому что он добавляет только те реализации, которые вы в противном случае написали бы сами, и, если вы пишете библиотеку, он прозрачен для пользователей вашей библиотеки. snafu, возможно, более мощный - он генерирует намного больше кода, но самоуверен в том, что он генерирует, поэтому вам нужно будет привыкнуть к его парадигме, чтобы в полной мере использовать его, и концепции snafu станут частью API вашей библиотеки.

Используя thiserror, приведенный выше код сокращается до:

use thiserror::Error;

#[derive(Debug, Error)]
enum Error {
    #[error("IO Error: {0})]
    Io(#[from] io::Error),
    #[error("ParseInt Error: {0})]
    ParseInt(#[from] num::ParseIntError)
}

Обратите внимание, что это также генерирует Display реализации, которые я не включил выше.

...