Простая обработка ошибок при проверке предусловий/аргументов в Rust

Есть ли способ максимально просто написать обработку ошибок с помощью expect? Я часто хочу проверить входные данные программы и прервать программу с сообщением об ошибке, если введенные данные недействительны.

Пример с expect:

let url = Url::parse("sdfsf")
    .expect("Error parsing URL");
// Output:
// thread 'main' panicked at src\main.rs:41:35:
// Error parsing URL: RelativeUrlWithoutBase

Из этого ответа я понял, что мне не следует использовать expect для реальных ошибок пользователя, так как я могу воспроизвести эту простоту с помощью ориентированного на пользователя дисплея?

Лучшее, что я придумал, это unwrap_or_else:

let url = Url::parse("sdfsf")
    .unwrap_or_else(|e| {
      eprintln!("Error parsing URL: {}", e);
      std::process::exit(1);
    });
// Output:
// Error parsing URL: relative URL without a base

Я читал, что выход не вызывает деструкторы, так что, возможно, это вызовет у меня проблемы позже.


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

use clap::Parser;
use std::collections::HashMap;
use std::process::Command;
use url::Url;

#[derive(Parser, Debug)]
struct Args {
    /// Url with log information to view.
    #[arg(short, long)]
    url: String,
}

fn main() {
    let args = Args::parse();

    let url = Url::parse(args.url.as_str()).expect("Error parsing URL");

    let query: HashMap<String, String> = url.query_pairs().into_owned().collect();

    let server_url = query
        .get("server_url")
        .expect("No server_url specified in url.");

    let revision = query
        .get("revision")
        .expect("No revision specified in url.");

    Command::new("TortoiseProc.exe")
        .arg("/command:log")
        .arg(format!("/startrev:{revision}"))
        .arg(format!("/path:{server_url}"))
        .output()
        .expect("Failed to execute command.");
}

Related but unhelpful questions: Using unwrap_or_else for error handling in Rust , More compact, easier to read Rust error handling (neither actually answer their titular question). How to do error handling in Rust and what are the common pitfalls? doesn't address the "print error" part of my error handling.

Пожалуйста, прочитайте главу об обработке ошибок из The Rust Book.

Aleksander Krauze 25.03.2024 08:03
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
1
1
125
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Прочитав глубже ответ Хаима Фридмана, я понял, что могу написать свою собственную развертку, которая умирает при сбоях:

use url::Url;

pub trait UnwrapExt<T> {
    fn unwrap_or_error(self, msg: &str) -> T;
}

impl<T, E: std::fmt::Display> UnwrapExt<T> for Result<T, E> {
    fn unwrap_or_error(self, msg: &str) -> T {
        self.unwrap_or_else(|err| {
            eprintln!("{msg}: {}", { err });
            std::process::exit(1);
        })
    }
}

impl<T> UnwrapExt<T> for Option<T> {
    fn unwrap_or_error(self, msg: &str) -> T {
        self.unwrap_or_else(|| {
            eprintln!("{msg}");
            std::process::exit(1);
        })
    }
}

fn main() {
    let url = Url::parse("sdsdf")
        .unwrap_or_error("Error parsing URL");

    println!("{}", url.scheme());
}
// Output:
// Error parsing URL: relative URL without a base

Это хорошо работает в данном случае!

Это работает для вашего случая, но не является идиоматическим или компонуемым, поскольку не позволяет обрабатывать ошибки вверх по стеку. Обычно ошибки распространяются вверх и в конечном итоге отображаются на верхнем уровне. Вот гораздо более короткий код, который достигает той же цели: play.rust-lang.org/…

user4815162342 25.03.2024 08:21

Как можно обрабатывать ошибки в стеке, когда я проверяю входные данные программы в основной части? Он не может подняться выше, верно? Вся моя цель — обрабатывать ошибки и прерывать работу. Я обновил свой вопрос полной программой. Однако anyhow::Context выглядит красиво — кажется, что он включает сообщение об ошибке без потери объекта результата.

idbrii 26.03.2024 06:49

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

user4815162342 26.03.2024 09:41

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

idbrii 27.03.2024 00:26

Теперь я добавил ответ. Первоначально я этого не сделал, потому что чувствовал, что это достаточно описано в существующих ответах. Но теперь я вижу, что наглядная демонстрация anyhow действительно повышает ценность вашего конкретного варианта использования. Спасибо за изящное принятие критики и приятного изучения Rust!

user4815162342 27.03.2024 09:27

Для этого вы можете использовать оператор let foo = match bar {}.

let url = match Url::parse("sdfsf") {
  Ok(url) => url,
  Err(error) => panic!{"Error parsing URL: {:?}", error}
}
    

Этот способ также позволяет вам выполнить дополнительный код в случае Ok(()) или Err(()), например:

let url = match Url::parse("sdfsf") {
  Ok(url) => {
    println!("URL parsed successfully!");
    url
  },
  Err(error) => panic!{"Error parsing URL: {:?}", error}
}
    

Этот код можно улучшить с помощью let-else.

Chayim Friedman 25.03.2024 15:12

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

willxw 25.03.2024 15:16

Моя ошибка, вы печатаете ошибку, поэтому она не может.

Chayim Friedman 25.03.2024 15:32

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

idbrii 26.03.2024 06:41
Ответ принят как подходящий

Самый простой способ распространения ошибок в Rust — использовать оператор ?. Библиотека anyhow делает это очень удобным, предоставляя тип, который обертывает Box<dyn Error>, что позволяет распространять различные ошибки в простой тип, который содержит только сообщение об ошибке. У него также есть метод context(), который добавляет контекст к ошибке. Поскольку main() может возвращать Result, пример использования может выглядеть так:

use url::Url;
use anyhow::Context;

fn main() -> anyhow::Result<()> {
    let url = Url::parse("sdsdf").context("Error parsing URL")?;
    println!("{}", url.scheme());
    Ok(())
}

Другие вопросы по теме