Запись сообщений ошибок в поток ошибок вместо стандартного потока вывода

В данный момент мы записываем весь наш вывод в терминал, используя функцию println!. В большинстве терминалов предоставлено два вида вывода: стандартный поток вывода ( stdout ) для общей информации и стандартный поток ошибок ( stderr ) для сообщений об ошибках. Это различие позволяет пользователям выбирать, направлять ли успешный вывод программы в файл, но при этом выводить сообщения об ошибках на экран.

Функция println! может печатать только в стандартный вывод, поэтому мы должны использовать что-то ещё для печати в стандартный поток ошибок.

Проверка, куда записываются ошибки

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

Ожидается, что программы командной строки будут отправлять сообщения об ошибках в стандартный поток ошибок, поэтому мы все равно можем видеть сообщения об ошибках на экране, даже если мы перенаправляем стандартный поток вывода в файл. Наша программа в настоящее время не ведёт себя правильно: мы увидим, что она сохраняет вывод сообщения об ошибке в файл!

Чтобы продемонстрировать это поведение, мы запустим программу с помощью > и именем файла output.txt в который мы хотим перенаправить стандартный поток вывода. Мы не будем передавать никаких аргументов, что должно вызвать ошибку:

$  cargo run > output.txt

Синтаксис > указывает оболочке записывать содержимое стандартного вывода в output.txt вместо экрана. Мы не увидели сообщение об ошибке, которое мы ожидали увидеть на экране, так что это означает, что оно должно быть в файле. Вот что содержит output.txt:

Problem parsing arguments: not enough arguments

Да, наше сообщение об ошибке выводится в стандартный вывод. Гораздо более полезнее, чтобы подобные сообщения об ошибках печатались в стандартной поток ошибок, поэтому в файл попадают только данные из успешного запуска. Мы поменяем это.

Печать ошибок в поток ошибок

Мы будем использовать код в листинге 12-24, чтобы изменить способ вывода сообщений об ошибках. Из-за рефакторинга, который мы делали ранее в этой главе, весь код, который печатает сообщения об ошибках, находится в одной функции: main. Стандартная библиотека предоставляет макрос eprintln!который печатает в стандартный поток ошибок, поэтому давайте изменим два места, где мы вызывали println! для печати ошибок, чтобы использовать eprintln! вместо этого.

Файл: src/main.rs

use std::env;
use std::error::Error;
use std::fs;
use std::process;

use minigrep::{search, search_case_insensitive};

fn main() {
    let args: Vec<String> = env::args().collect();

    let config = Config::build(&args).unwrap_or_else(|err| {
        eprintln!("Problem parsing arguments: {err}");
        process::exit(1);
    });

    if let Err(e) = run(config) {
        eprintln!("Application error: {e}");
        process::exit(1);
    }
}

pub struct Config {
    pub query: String,
    pub file_path: String,
    pub ignore_case: bool,
}

impl Config {
    fn build(args: &[String]) -> Result<Config, &'static str> {
        if args.len() < 3 {
            return Err("not enough arguments");
        }

        let query = args[1].clone();
        let file_path = args[2].clone();

        let ignore_case = env::var("IGNORE_CASE").is_ok();

        Ok(Config {
            query,
            file_path,
            ignore_case,
        })
    }
}

fn run(config: Config) -> Result<(), Box<dyn Error>> {
    let contents = fs::read_to_string(config.file_path)?;

    let results = if config.ignore_case {
        search_case_insensitive(&config.query, &contents)
    } else {
        search(&config.query, &contents)
    };

    for line in results {
        println!("{line}");
    }

    Ok(())
}

Запись сообщений об ошибках в Standard Error вместо Standard Output используя eprintln!

Давайте снова запустим программу таким же образом, без каких-либо аргументов и перенаправим стандартный вывод с помощью >:

$ cargo run > output.txt
Problem parsing arguments: not enough arguments

Теперь мы видим ошибку на экране и output.txt не содержит ничего, что мы ожидаем от программы командной строки.

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

$ cargo run -- to poem.txt > output.txt

Мы не увидим никакого вывода в терминал, а output.txt будет содержать наши результаты:

Файл: output.txt

Are you nobody, too?
How dreary to be somebody!

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

Итоги

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

Далее мы рассмотрим некоторые возможности Rust, на которые повлияли функциональные языки: замыкания и итераторы.