Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Вывод ошибок в stderr

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

Макрос println! умеет выводить только в стандартный вывод, так что для вывода в стандартный поток ошибок понадобится что-то другое.

Куда сейчас пишутся ошибки

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

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

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

$ cargo run > output.txt

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

Problem parsing arguments: not enough arguments

Так и есть: сообщение об ошибке выводится в стандартный вывод. Куда полезнее, чтобы такие сообщения шли в стандартный поток ошибок, а в файл попадали только данные успешного запуска. Исправим это.

Выводим ошибки в стандартный поток ошибок

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

Файл: 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(())
}
Листинг 12-24: Пишем сообщения об ошибках не в стандартный вывод, а в стандартный поток ошибок с помощью 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, появившиеся под влиянием функциональных языков: замыкания и итераторы.