Вывод ошибок в 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!, выводившие ошибки.
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(())
}
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, появившиеся под влиянием функциональных языков: замыкания и итераторы.