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

Перенаправление ошибок в стандартный поток ошибок

Сейчас мы записываем весь наш вывод в терминал с помощью макроса 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!.

Filename: 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(())
}
Listing 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, на которые повлияли функциональные языки: замыкания и итераторы.